These are the questions that actually come up for mid-level Python backend roles — grouped by topic, each with a short model answer and a note on what the interviewer is really testing.
Memorising answers won't get you the offer; interviewers can tell. Use these to check your understanding and, more importantly, to rehearse saying them out loud. For each one, notice the pattern of a strong answer: state the mechanism, give the trade-off, then stop.
Python core
What is a decorator, and when would you use one?
A decorator is a callable that takes a function and returns a new function wrapping it, so you can add behaviour without changing the original body. Common uses: caching, access checks, logging, timing, retries. In practice you write @decorator above the function; underneath it's just fn = decorator(fn).
What they're testing: whether you understand functions as first-class objects — not just the syntax.
What is the GIL and when does it actually matter?
The Global Interpreter Lock lets only one thread execute Python bytecode at a time, so threads don't give you true parallelism for CPU-bound work. It rarely hurts I/O-bound work (the lock is released during I/O), so threads are still fine for network calls. For CPU-bound parallelism, use multiprocessing or a native/extension path.
What they're testing: that you know when threads help (I/O) versus when to reach for processes (CPU).
Generator vs list — why use yield?
A list holds all items in memory at once; a generator produces them lazily, one at a time, so it uses near-constant memory and can stream huge or infinite sequences. Trade-off: you can only iterate a generator once, and you can't index it. Use generators for large pipelines, lists when you need random access or to reuse the data.
What they're testing: memory awareness and knowing the trade-off, not just the definition.
Why are mutable default arguments dangerous?
A default like def f(x, items=[]) is evaluated once at definition time, so the same list is shared across every call — mutating it leaks state between calls. The fix is items=None then items = items or [] inside the function.
What they're testing: a classic gotcha that shows you understand when Python evaluates defaults.
Django & frameworks
select_related vs prefetch_related (and the N+1 problem)
The N+1 problem is when you load a list, then hit the DB once per item for a related object — 1 + N queries. select_related fixes it for foreign-key / one-to-one by doing a SQL JOIN (one query). prefetch_related handles many-to-many / reverse FKs with a second query that Python joins in memory. Reach for them whenever you loop over a queryset touching related data.
What they're testing: whether you can spot and fix the most common ORM performance bug.
What happens when a request hits your Django app?
The WSGI/ASGI server hands the request to Django, which runs it through the middleware stack, resolves the URL to a view, the view does its work (often via the ORM), returns a response, and middleware runs again on the way out. Naming middleware, URL resolution, view, and response is enough to show you understand the flow.
What they're testing: a mental model of the framework, not memorised internals.
Databases & APIs
What is a database index, and when would you avoid one?
An index is a separate structure (usually a B-tree) that speeds up reads by avoiding a full table scan. The trade-off is slower writes and extra storage, since every insert/update maintains the index. Avoid one on a small table or on a column that's written far more than it's read. Short version: index for read-heavy lookups, skip it when writes dominate.
What they're testing: that you weigh read speed against write cost, not just "indexes make things fast".
What does idempotency mean in a REST API?
An idempotent request produces the same result no matter how many times it's sent. GET, PUT and DELETE should be idempotent; POST usually isn't. It matters for retries — if a client retries after a timeout, an idempotent endpoint won't create duplicates. For non-idempotent creates, an idempotency key is the common fix.
What they're testing: API design maturity and thinking about failure/retries.
An endpoint is slow. How do you approach it?
Measure first — find whether it's the DB, the code, or an external call. The usual culprits for a Python backend: N+1 queries (fix with select_related/prefetch_related), a missing index, or fetching more data than needed. Then consider caching the result if it's read-heavy and can tolerate slight staleness. Name the order — measure, fix the query, then cache — rather than jumping to "add Redis".
What they're testing: a systematic debugging process, not a memorised list of fixes.
How to actually answer these
The difference between a pass and a fail here is rarely the facts — it's delivery. Open with the mechanism, name the trade-off out loud, and stop when you've answered. If you don't know something, say what you'd expect and why instead of bluffing. If you tend to blank under pressure, that's a separate, fixable skill.
I built Peakblick, where you rehearse questions like these on a timer and an AI scores every answer 1–10 with feedback. Free to try.
Top comments (0)