Python backend interviews cover language internals, async programming, Django or FastAPI, and database access patterns. Below are the most common questions with model answers. Middle: deeper understanding, optimization, and real-world situations.
1
What does the GIL actually prevent, and when do threads still help?
Answer
The Global Interpreter Lock allows only one thread to execute Python bytecode at a time, so pure Python code never runs in parallel across cores. Threads still help whenever the work is waiting rather than computing — network calls, disk, database — because the lock is released during blocking I/O. For CPU-bound work you need processes, a C extension that drops the GIL, or to move the hot part out of Python.
2
What does await actually do, and when is async pointless?
Answer
await yields control back to the event loop so it can run something else until the awaited operation completes; it does not create a thread and nothing runs in parallel. Async is pointless when the work is CPU-bound, because a long computation blocks the loop and every other task with it. It is also pointless if any library in the path is synchronous — one blocking database driver stalls the whole loop.
3
Write a decorator. Why does it need functools.wraps?
Answer
A decorator is a function that takes a function and returns a replacement, usually a closure that calls the original. Without functools.wraps the replacement carries its own name and docstring, so the decorated function reports itself as 'wrapper' — which breaks introspection, help(), and any framework that routes by function name. wraps copies the metadata across.
4
You have an N+1 problem in the ORM. How do you find it and fix it?
Answer
You find it by looking at the query log or the debug toolbar and seeing one query per row of a list. The cause is lazy loading inside a loop. select_related solves it for forward foreign keys with a join; prefetch_related solves it for reverse and many-to-many with a second query and an in-memory match. The trap is fixing the symptom by caching the result instead of the query.
5
When would you return a generator instead of a list?
Answer
When the caller iterates once and the data does not need to fit in memory — reading a large file, streaming query results, a pipeline of transformations. The cost is that a generator is consumed: you cannot take its length, index it, or iterate it twice without materialising it. Returning one from a public API surprises callers who expect a sequence, so it should be a documented decision.
6
What does a context manager guarantee?
Answer
That __exit__ runs whether the block finished normally or raised, which is why file handles, locks and transactions belong in one. Written with contextlib.contextmanager, the code before yield is setup and the code after is teardown — but only if the teardown is in a finally, otherwise an exception skips it. The guarantee is about cleanup, not about suppressing the error.
7
What do you mock in a test, and what do you not?
Answer
Mock what you do not own and cannot control: third-party APIs, the clock, randomness, email delivery. Do not mock your own domain logic, because then the test asserts that your mock behaves the way you wrote it. Mocking the database is usually a mistake too — a transaction rolled back after each test gives real behaviour at little cost, and ORM bugs are exactly the ones mocks hide.
🦎
Reading answers is not enough
In a real interview you speak under pressure. Cam asks these same questions, scores every answer, and shows exactly what to fix.
Practice a Middle Python Backend interview →Free · 3 interviews per month