Skip to content
Laviyx Learn

Try

Python Interview Questions

Frequently asked Python interview questions on data types, functions, and core behaviour, with hints, answers, and the reasoning interviewers look for.

23 questions · Updated

  1. 1.What is the difference between a list and a tuple?

    BeginnerConceptData structures
    Show hint

    One of them can be changed after it is created.

    Show solution
    • A list ([1, 2, 3]) is mutable: you can add, remove and change items.
    • A tuple ((1, 2, 3)) is immutable: once created, it cannot be changed.

    Use tuples for fixed groups of values (coordinates, a database row, multiple return values). Because they are immutable, tuples can be used as dictionary keys and set members; lists cannot.

  2. 2.What is the difference between == and is?

    BeginnerConceptFundamentals
    Show hint

    One compares values, the other compares identity.

    Show solution
    • == checks whether two objects have equal values.
    • is checks whether two names refer to the same object in memory.
    Python
    a = [1, 2]
    b = [1, 2]
    print(a == b)  # True: same contents
    print(a is b)  # False: two separate lists

    Use is only for singletons such as None (if x is None:). Using is to compare numbers or strings can appear to work for small values because of internal caching, but it is not reliable.

  3. 3.What does this code print, and why?

    IntermediateOutput predictionFunctions
    Show hint

    When is the default list created?

    Show solution
    Python
    def add(item, items=[]):
        items.append(item)
        return items
    
    print(add(1))
    print(add(2))

    It prints [1] and then [1, 2]. Default values are evaluated once, when the function is defined, so every call that uses the default shares the same list. The fix:

    Python
    def add(item, items=None):
        if items is None:
            items = []
        items.append(item)
        return items
  4. 4.Reverse a string without using reversed() or [::-1].

    BeginnerCodingStrings
    Show hint

    Build a new string by putting each character in front of the result so far.

    Show solution
    Python
    def reverse(text):
        result = ""
        for ch in text:
            result = ch + result
        return result
    
    print(reverse("python"))  # nohtyp

    In real code, text[::-1] is the idiomatic answer. Interviewers ask for the loop version to see that you understand iteration. Mention that repeated string concatenation creates a new string each time; for very long inputs, collecting characters in a list and using "".join(...) is more efficient.

  5. 5.Count how many times each word appears in a sentence.

    IntermediateCodingDictionaries
    Show hint

    A dictionary maps each word to its count.

    Show solution
    Python
    def word_counts(sentence):
        counts = {}
        for word in sentence.lower().split():
            counts[word] = counts.get(word, 0) + 1
        return counts
    
    print(word_counts("the cat and the hat"))
    # {'the': 2, 'cat': 1, 'and': 1, 'hat': 1}

    Also mention the standard-library shortcut: collections.Counter(sentence.lower().split()).

  6. 6.What is the difference between a shallow copy and a deep copy?

    IntermediateConceptData structures
    Show hint

    Think about a list that contains other lists.

    Show solution
    • A shallow copy (list.copy(), copy.copy(), lst[:]) creates a new outer container, but the items inside are still shared with the original.
    • A deep copy (copy.deepcopy()) recursively copies everything, so nothing is shared.
    Python
    import copy
    
    grid = [[1, 2], [3, 4]]
    shallow = grid.copy()
    deep = copy.deepcopy(grid)
    
    grid[0][0] = 99
    print(shallow[0][0])  # 99, the inner list is shared
    print(deep[0][0])     # 1
  7. 7.What are *args and **kwargs?

    IntermediateConceptFunctions
    Show hint

    They let a function accept a variable number of arguments.

    Show solution
    • *args collects extra positional arguments into a tuple.
    • **kwargs collects extra keyword arguments into a dictionary.
    Python
    def report(*args, **kwargs):
        print(args)
        print(kwargs)
    
    report(1, 2, unit="kg", rounded=True)
    # (1, 2)
    # {'unit': 'kg', 'rounded': True}

    The names args and kwargs are only conventions; the * and ** are what matter.

  8. 8.Which Python types are mutable, and which are immutable?

    BeginnerConceptData types
    Show hint

    Can the object be changed in place after it is created?

    Show solution
    • Immutable: int, float, bool, str, tuple, frozenset, bytes, None. "Changing" them always creates a new object.
    • Mutable: list, dict, set, bytearray and most objects of your own classes.
    Python
    name = "asha"
    upper = name.upper()   # a new string; name is unchanged
    print(name, upper)     # asha ASHA
    
    nums = [1, 2]
    alias = nums
    alias.append(3)        # changes the one shared list
    print(nums)            # [1, 2, 3]

    This matters for function arguments (a function can change a list you pass in), default arguments, and dictionary keys (keys must be immutable, or more precisely hashable).

  9. 9.What does if __name__ == "__main__" do?

    BeginnerConceptModules
    Show hint

    What is the value of name when a file is run directly, compared with when it is imported?

    Show solution

    Every module has a __name__ variable. When you run a file directly (python report.py), Python sets it to "__main__". When the file is imported (import report), it is set to the module name, "report".

    Python
    def main():
        print("Generating report...")
    
    if __name__ == "__main__":
        main()

    So code under that if runs only when the file is executed as a script, not when another file imports its functions. It lets one file work both as a reusable module and as a program.

  10. 10.Explain try, except, else and finally.

    BeginnerConceptExceptions
    Show hint

    Which block runs when there is no error, and which block runs every time?

    Show solution
    Python
    try:
        value = int("42")       # code that might fail
    except ValueError:
        print("Not a number")   # runs only if that error happened
    else:
        print("Got", value)     # runs only if NO exception happened
    finally:
        print("Done")           # always runs, error or not

    Output: Got 42 and then Done.

    • Catch specific exceptions (ValueError), not a bare except:, which also hides bugs and even catches KeyboardInterrupt.
    • Keep the try block small, so you only catch errors from the line you expect to fail.
    • finally is for clean-up, such as closing a connection; with statements often do this for you.
  11. 11.Remove duplicates from a list while keeping the original order.

    BeginnerCodingData structures
    Show hint

    A set forgets the order. Track what you have already seen, or use dictionary keys.

    Show solution
    Python
    def dedupe(items):
        seen = set()
        result = []
        for item in items:
            if item not in seen:
                seen.add(item)
                result.append(item)
        return result
    
    print(dedupe([3, 1, 3, 2, 1]))      # [3, 1, 2]
    
    # One-liner: dict keys are unique and keep insertion order (Python 3.7+)
    print(list(dict.fromkeys([3, 1, 3, 2, 1])))   # [3, 1, 2]

    Both run in O(n) time, because checking membership in a set or dict is O(1) on average. list(set(items)) also removes duplicates but does not keep the order.

  12. 12.Write a function that checks whether two words are anagrams.

    BeginnerCodingStrings
    Show hint

    Two words are anagrams if they contain exactly the same letters the same number of times.

    Show solution
    Python
    from collections import Counter
    
    def is_anagram(a, b):
        a = a.replace(" ", "").lower()
        b = b.replace(" ", "").lower()
        return Counter(a) == Counter(b)
    
    print(is_anagram("Listen", "Silent"))   # True
    print(is_anagram("Dormitory", "Dirty room"))  # True
    print(is_anagram("hello", "world"))     # False

    Comparing sorted(a) == sorted(b) also works and is O(n log n); counting with Counter is O(n).

  13. 13.Find the first character in a string that does not repeat.

    IntermediateCodingStrings
    Show hint

    Count every character first, then scan the string again in order.

    Show solution
    Python
    from collections import Counter
    
    def first_unique(text):
        counts = Counter(text)
        for ch in text:
            if counts[ch] == 1:
                return ch
        return None
    
    print(first_unique("swiss"))      # w
    print(first_unique("aabb"))       # None

    Two passes over the string give O(n) time. Calling text.count(ch) inside the loop would also work but is O(n²), which interviewers will point out.

  14. 14.Merge two sorted lists into one sorted list without calling sort().

    IntermediateCodingAlgorithms
    Show hint

    Walk through both lists with two indexes, always taking the smaller current item.

    Show solution
    Python
    def merge(a, b):
        i = j = 0
        result = []
        while i < len(a) and j < len(b):
            if a[i] <= b[j]:
                result.append(a[i])
                i += 1
            else:
                result.append(b[j])
                j += 1
        result.extend(a[i:])   # whatever is left over
        result.extend(b[j:])
        return result
    
    print(merge([1, 4, 9], [2, 3, 10, 12]))   # [1, 2, 3, 4, 9, 10, 12]

    This runs in O(n + m) time and is the core step of merge sort. In real code, heapq.merge(a, b) does the same lazily.

  15. 15.What does this code print, and why?

    IntermediateOutput predictionData structures
    Show hint

    How many inner lists does [[0] * 3] * 3 actually create?

    Show solution
    Python
    grid = [[0] * 3] * 3
    grid[0][0] = 1
    print(grid)

    It prints [[1, 0, 0], [1, 0, 0], [1, 0, 0]]. The outer * 3 copies the reference to one inner list three times, so all three rows are the same list. Create separate rows with a comprehension:

    Python
    grid = [[0] * 3 for _ in range(3)]
    grid[0][0] = 1
    print(grid)   # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

    The inner [0] * 3 is safe because integers are immutable.

  16. 16.What does this code print, and how do you fix it?

    AdvancedOutput predictionFunctions
    Show hint

    When does each lambda look up the value of i, when it is created or when it is called?

    Show solution
    Python
    funcs = [lambda: i for i in range(3)]
    print([f() for f in funcs])

    It prints [2, 2, 2], not [0, 1, 2]. Closures look up variables when they are called, not when they are created, and by then the loop has finished with i = 2.

    Fix it by binding the current value as a default argument, which is evaluated at creation time:

    Python
    funcs = [lambda i=i: i for i in range(3)]
    print([f() for f in funcs])   # [0, 1, 2]

    functools.partial is another common fix.

  17. 17.What is the difference between a list comprehension and a generator expression?

    IntermediateConceptGenerators
    Show hint

    Look at the brackets, and think about when the values are produced.

    Show solution
    Python
    squares_list = [x * x for x in range(1_000_000)]   # builds all 1,000,000 values now
    squares_gen = (x * x for x in range(1_000_000))    # produces values one at a time
    
    print(sum(squares_gen))   # 333332833333500000
    • A list comprehension [...] creates the whole list in memory immediately. You can index it, take its len(), and loop over it many times.
    • A generator expression (...) is lazy: it produces each value only when asked, using almost no memory. It can be consumed only once.

    Use a generator when you only need to loop over the values once, especially for large data or when feeding a function like sum(), max() or any().

  18. 18.What does yield do, and how is a generator different from a normal function?

    IntermediateConceptGenerators
    Show hint

    What happens to the function's local variables between values?

    Show solution

    A function that contains yield becomes a generator function. Calling it does not run the body; it returns a generator object. Each next() runs the body until the next yield, hands out that value, and pauses, keeping all local variables, until the next value is requested.

    Python
    def count_up_to(n):
        i = 1
        while i <= n:
            yield i
            i += 1
    
    gen = count_up_to(3)
    print(next(gen))    # 1
    print(list(gen))    # [2, 3]  (continues where it paused)

    A normal function runs to completion and returns once. Generators are ideal for large or infinite sequences and for pipelines that process data step by step.

  19. 19.What is a decorator? Write one that measures how long a function takes.

    IntermediateCodingDecorators
    Show hint

    A decorator is a function that takes a function and returns a new function that wraps it.

    Show solution
    Python
    import functools
    import time
    
    def timer(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed = time.perf_counter() - start
            print(f"{func.__name__} took {elapsed:.4f}s")
            return result
        return wrapper
    
    @timer
    def slow_sum(n):
        return sum(range(n))
    
    slow_sum(1_000_000)   # prints something like: slow_sum took 0.0150s

    @timer above a function is shorthand for slow_sum = timer(slow_sum). Key points to mention: *args, **kwargs lets the wrapper accept any arguments, returning result keeps the original return value, and functools.wraps preserves the original function's name and docstring.

  20. 20.What is the difference between an instance method, a @classmethod and a @staticmethod?

    IntermediateConceptOOP
    Show hint

    Look at what each one receives as its first argument.

    Show solution
    Python
    class Temperature:
        def __init__(self, celsius):
            self.celsius = celsius
    
        def to_fahrenheit(self):              # instance method: gets self
            return self.celsius * 9 / 5 + 32
    
        @classmethod
        def from_fahrenheit(cls, f):          # class method: gets the class
            return cls((f - 32) * 5 / 9)
    
        @staticmethod
        def is_freezing(celsius):             # static method: gets nothing extra
            return celsius <= 0
    
    t = Temperature.from_fahrenheit(212)
    print(t.celsius, t.to_fahrenheit())       # 100.0 212.0
    print(Temperature.is_freezing(-5))        # True
    • Instance methods work with one object's data.
    • Class methods receive the class (cls), so they are used as alternative constructors and work correctly with subclasses.
    • Static methods are plain functions placed in the class for organisation; they cannot access the instance or the class.
  21. 21.Why can a tuple be a dictionary key but a list cannot?

    IntermediateConceptData structures
    Show hint

    Dictionaries find keys by their hash. What would happen if a key changed after it was stored?

    Show solution

    Dictionary keys (and set members) must be hashable: they need a hash value that never changes during their lifetime, and they must be comparable with ==. A dictionary uses the hash to decide where to store the key.

    Lists are mutable, so their contents (and therefore any hash based on them) could change after being stored, and the dictionary would no longer find them. Python prevents this by making lists unhashable:

    Python
    locations = {(19.07, 72.87): "Mumbai"}   # tuple key: fine
    try:
        bad = {[19.07, 72.87]: "Mumbai"}
    except TypeError as error:
        print(type(error).__name__)           # TypeError (unhashable type: 'list')

    A tuple is hashable only if everything inside it is hashable, so ([1, 2], 3) cannot be a key either.

  22. 22.What does the with statement do, and how do you write your own context manager?

    IntermediateConceptContext managers
    Show hint

    It guarantees that set-up and clean-up code run, even when an error happens.

    Show solution

    with calls the object's __enter__ method at the start of the block and its __exit__ method at the end, even if an exception is raised inside. That is why with open(...) always closes the file.

    The easiest way to write one is contextlib.contextmanager:

    Python
    from contextlib import contextmanager
    
    @contextmanager
    def opened(name):
        print("open", name)
        try:
            yield name          # the value bound by "as"
        finally:
            print("close", name)
    
    with opened("data.csv") as f:
        print("using", f)

    This prints open data.csv, using data.csv, close data.csv. A class with __enter__ and __exit__ methods does the same job and is better when the manager needs to hold more state.

  23. 23.What is the GIL, and how does it affect threads in Python?

    AdvancedConceptConcurrency
    Show hint

    Think about CPU-heavy work versus work that waits for the network or disk.

    Show solution

    The Global Interpreter Lock is a lock in CPython (the standard Python implementation) that allows only one thread to run Python bytecode at a time, which keeps its memory management simple and safe.

    • For I/O-bound work (downloading files, calling APIs, waiting for a database), threads still help a lot, because a thread releases the GIL while it waits. threading or asyncio work well here.
    • For CPU-bound work (heavy calculations in pure Python), threads do not run in parallel, so they give little or no speed-up. Use multiprocessing or concurrent.futures.ProcessPoolExecutor, which run separate processes, each with its own interpreter and GIL. Libraries like NumPy also release the GIL inside their C code.

    Since Python 3.13 there is also an optional "free-threaded" build without the GIL (experimental in 3.13, officially supported from 3.14), but the default build still has it, and some C extensions do not support free-threading yet.

Review the lessons