Shpora.tech

Blog

Python interview questions: 25 questions with the reasoning behind them

Not a list of definitions to memorise, but what each question is really testing — and how to answer so that it shows.

There is no shortage of "100 Python questions" lists, and they help less than they promise: a memorised definition of a decorator is audible from the first sentence. The interviewer almost always asks the next question — "so why do you need functools.wraps?" — and that is where the script runs out.

Data model

Which types are mutable?

Why a list changes and a string does not Immutable int, str, tuple, frozenset, bytes An operation builds a new object, the original stays as it was Mutable list, dict, set, bytearray Changed in place — the caller sees the change too Hence the classic trap, a mutable default argument: def f(x, acc=[]): # the list is built ONCE, when the function is defined acc.append(x) # and accumulates across calls Correct: acc=None, then inside — if acc is None: acc = []
Mutable and immutable types in Python: what happens when you pass them to a function

Immutable: int, float, str, tuple, frozenset, bytes. Mutable: list, dict, set, bytearray.

A good answer does not stop at the list — it goes straight to the consequence: a mutable object passed into a function is changed for the caller too.

What is wrong with this code?

def add_item(item, basket=[]):
    basket.append(item)
    return basket

The list is created once, when the function is defined, not on each call. So the second call returns both items, which is never what anyone wanted.

def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

How does is differ from ==?

== compares values, is compares identity. The follow-up is usually about small integers: 256 is 256 is True, 257 is 257 is not, because CPython caches integers from −5 to 256. The right conclusion is not "memorise this" but "which is why is is only used against None, True and False".

Functions and decorators

What is a decorator?

import functools, time

def timed(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return fn(*args, **kwargs)
        finally:
            print(f"{fn.__name__}: {time.perf_counter() - start:.3f}s")
    return wrapper

Two details carry the answer. functools.wraps copies the name and docstring onto the wrapper — without it fn.__name__ becomes "wrapper", breaking anything that relies on introspection. And try/finally means the timing still prints when the call raises.

What is a closure?

def counter():
    n = 0
    def inc():
        nonlocal n   # without nonlocal: UnboundLocalError
        n += 1
        return n
    return inc

nonlocal is the reason the question gets asked.

Generators

How does a generator differ from a list?

It produces items one at a time and never holds them all. A list comprehension over ten million items costs hundreds of megabytes; the generator expression costs one item's worth.

sum(x * x for x in range(10_000_000))    # constant memory
sum([x * x for x in range(10_000_000)])  # roughly 400 MB

The price: you cannot iterate twice and you cannot call len().

What is the iterator protocol?

An object is iterable if it has __iter__ returning an iterator. An iterator has __next__, which yields the next item and raises StopIteration when exhausted. A for loop is sugar over exactly that.

The GIL

What is the GIL and what does it prevent?

The Global Interpreter Lock means only one thread executes CPython bytecode at a time, so threads do not speed up computation across cores.

The follow-up is always: then why have threads at all? Because the GIL is released during I/O. While one thread waits on the network or disk, others run — so threads help with API calls and not with matrix multiplication.

For computation: multiprocessing, or libraries that release the GIL in C code such as NumPy. For I/O: threads or asyncio. Python 3.13 ships an experimental free-threaded build, worth mentioning but not worth building an answer on.

How does asyncio differ from threads?

Threads are switched by the operating system at arbitrary points; coroutines are switched by the event loop, and only at an await. So between two awaits you know you were not interrupted, which removes a whole class of races. The cost: one blocking call stalls the entire loop.

Classes

What is the MRO?

Method Resolution Order — the order Python searches for a method under multiple inheritance, computed by the C3 algorithm and visible as __mro__. It is also the reason to call super().__init__() rather than Base.__init__(self): the latter breaks the chain in diamond inheritance.

staticmethod or classmethod?

@classmethod receives the class, so it behaves correctly under inheritance — the usual use is an alternative constructor. @staticmethod receives nothing and simply lives in the class namespace.

class User:
    @classmethod
    def from_json(cls, raw):
        return cls(**json.loads(raw))   # a subclass returns a subclass

How does a context manager work?

An object with __enter__ and __exit__. The second always runs, including on an exception, and suppresses it if it returns a truthy value.

from contextlib import contextmanager

@contextmanager
def transaction(conn):
    try:
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise

Practical questions

How would you find a bottleneck?

This is about method, not tooling. Measure first (cProfile, line_profiler), then look at algorithmic complexity, and only then at micro-optimisation. "I would rewrite it in C" without measurement is a bad sign.

Why is a dict better than a list for lookups?

Average constant time against a linear scan. On a thousand items nobody notices; on a million it decides everything. Worth adding that hash collisions degrade it to linear in the worst case, rare as that is in practice.

When you do not know the answer

It happens to everyone, and the reaction matters more than the knowledge. The worst option is inventing something: the interviewer knows this particular topic better than you, and it shows.

What works

"I have not used __slots__ in production. From the name, and from the fact that instances normally carry a __dict__, I would guess it fixes the attribute set and saves memory. Is that right?"

That is a strong answer — it shows you can reason from first principles rather than only recall.

In short

  • Every basic question has a follow-up: prepare the consequence, not the definition.
  • Mutable defaults, functools.wraps, nonlocal and the MRO are where experience shows.
  • The GIL blocks computation, not I/O.
  • Answer performance questions with "measure first".
  • Unknown territory: reason aloud from what you do know.

Shpora is an AI assistant that hears the interviewer's question and gives you something to build an answer on within a second. It runs on your own computer, with any video call.

Try it free
Read next

SQL interview questions: 20 problems with worked solutions