Python interview questions and where candidates fall apart
Everyone knows the definition from the docs. What eliminates candidates is the follow-up: \"fine, then why do threads speed up API calls if there's one GIL?\" The questions by level, with those follow-ups.

Question lists on the internet all work the same way: question, definition, next. In a real interview the definition is only the first move. The second one decides it: "fine, then why do threads speed up API calls if there's only one GIL?"
So the questions below come with the follow-ups that trail them, and with the places candidates usually come apart.
Junior: the language model
Mutable versus immutable types. What can be a dictionary key and why a list can't.
Follow-up: "Can a tuple?" Yes — if it contains no mutable elements. That's the question testing understanding rather than a memorised list.
Default arguments. The classic, asked nearly every time:
def add(item, target=[]):
target.append(item)
return target
add(1) # [1]
add(2) # [1, 2] — not [2]
The default is evaluated once when the function is defined, not on each call. The fix is target=None and creating the list inside.
Follow-up: "Where else does this bite?" — dataclasses, caches, module-level config.
is versus ==. Object identity against value equality.
The follow-up is almost always this:
a = 256; b = 256
a is b # True
a = 257; b = 257
a is b # False (in the REPL)
Small integers are cached by the interpreter. A candidate who knows why usually also understands the difference between an object and a name.
List comprehension versus generator. [x*x for x in range(10**7)] eats memory; (x*x for x in range(10**7)) doesn't.
Follow-up: "When is a generator worse?" When you need two passes over the data, or the length.
*args and **kwargs. Asked to find out whether you've read other people's code.
Mid-level: how it works inside
The GIL. The most common question at this level and the most poorly answered.
The definition: in CPython only one thread executes bytecode at a time. Then comes the part they're actually asking for.
Follow-up: "So why do threads help with network calls?" — because the GIL is released while waiting on I/O, letting other threads run.
Second follow-up: "And if the work is CPU-bound?" — threads won't help; you need processes (multiprocessing) or libraries that compute outside the interpreter.
Third, and current: "What changed in recent versions?" — 3.13 introduced an experimental free-threaded build without the GIL, and in 3.14 that mode became officially supported. Knowing this stands out, because most candidates answer from articles five years old.
Decorators. They want an explanation, not a recitation.
from functools import wraps
def retry(times):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
for attempt in range(times):
try:
return fn(*args, **kwargs)
except ConnectionError:
if attempt == times - 1:
raise
return wrapper
return decorator
Follow-up: "Why wraps?" — so the wrapped function keeps its name, docstring and signature. Candidates who write decorators without it usually copied them rather than understood them.
Generators and yield. Lazy evaluation, memory, yield from.
Follow-up: "What happens to the function's state between calls?" This is where you see whether someone understands that a generator is a suspended execution frame rather than a clever list.
Context managers. with, __enter__, __exit__, contextlib.contextmanager.
Follow-up: "What if the block raises?" — __exit__ runs anyway, which is the entire reason the construct exists.
Exceptions. The difference between except and finally, what else is for, and why except Exception: pass is a problem.
Senior: performance and the memory model
Asyncio. Event loop, coroutines, await.
The follow-up that matters: "Where doesn't asyncio help?" — CPU-bound work. And one blocking call inside a coroutine stalls the entire loop, which is the most common real-world mistake.
Memory management. Reference counting plus a cyclic garbage collector.
Follow-up: "What does __slots__ do?" — removes the per-instance __dict__, saving memory when you have many objects. And then: "What does it break?" — dynamic attribute assignment and some inheritance patterns.
MRO. Method resolution order and C3 linearisation under multiple inheritance.
Follow-up: "How do you inspect it?" — Class.__mro__. The question is rarely theoretical; it's usually checking whether you've untangled a real inheritance conflict.
Typing. typing, Protocol, what mypy buys you and what it doesn't.
Follow-up: "Do annotations affect runtime?" — no, they aren't enforced unless a library like Pydantic does it.
Performance. "The code is slow, what do you do?"
The right answer starts with measurement rather than optimisation: profile, find the hot spot, check algorithmic complexity, count the database queries. A candidate who immediately rewrites loops as comprehensions is showing they haven't met a real bottleneck.
Questions that aren't about knowledge
| Question | What's actually being watched |
|---|---|
| How would you test this function? | whether you think in boundaries and seams |
| Why did the project choose X over Y? | whether you can explain a decision rather than defend it |
| What would you rewrite in your last project? | whether you reflect |
| How would you explain this to a junior? | whether you can be simple |
These often weigh more than the technical ones: they're about working with you afterwards, and syntax is a week's study.
How to prepare
Don't memorise definitions. Half the questions above appear in every list, and interviewers know it — which is why the follow-up exists.
Drill the real wording. A question in an article and a question on the call sound different: live it comes shorter and with the follow-up attached. The question bank filters by language and grade, so you can drill your own section rather than a generic list.
Say it out loud. The gap between knowing something and explaining it in two minutes is large. Take five topics — the GIL, generators, decorators, asyncio, memory — and deliver each against a timer. Mock interviews with a debrief cover exactly that.
Prepare examples from your own code. One real case per topic. "We traced a leak to a closure in a decorator" outweighs a perfect definition.
Remember a prompt won't save the follow-up. A definition can be read off a screen; "so why do threads help with network calls" is where reading becomes audible. A prompt is insurance on an unfamiliar library, not a substitute for preparation.
FAQ
What do Python interviews ask about?
By level: junior — mutable versus immutable types, default arguments, is versus ==, generators; mid — the GIL, decorators, context managers, exceptions; senior — asyncio, the memory model, MRO, typing and how you approach performance.
What do they ask about the GIL?
The definition first: only one thread executes bytecode at a time in CPython. Then why threads still help with network work (the GIL is released during I/O), what to do with CPU-bound work (processes), and what changed recently — 3.13 shipped an experimental free-threaded build and 3.14 made that mode officially supported.
Why is a mutable default argument a bug?
The default is evaluated once at function definition, so the list survives between calls and accumulates data. Pass None and create the list inside the function.
How should I answer "the code is slow"?
By measuring first: profile, find the hot spot, check algorithmic complexity and the number of database queries. An answer that opens with rewriting loops signals no experience with real bottlenecks.
How deep do asyncio questions go?
Usually the event loop and coroutines are enough, but they'll nearly always ask where asyncio doesn't help: CPU-bound work, and a blocking call inside a coroutine that stalls the whole loop.
How do I prepare for a Python technical interview?
Don't memorise definitions — rehearse the follow-ups. For each topic, prepare a spoken explanation and one example from your own code. Five topics — the GIL, generators, decorators, asyncio, memory — cover most of a mid-level round.


