FastAPI
FastAPI is a thin layer, not a batteries-included framework. It takes Starlette for routing over ASGI and Pydantic for validation, adds dependency injection and OpenAPI generation, and stops there. There is no ORM, no migrations, no admin, and there never will be. So the first thing to understand about it: the speed does not come from FastAPI's own code but from the ASGI concurrency model plus Pydantic's compiled core — and both are easy to switch off with a single line of your own.
The second trait is that almost all of the framework's work is bound to the function signature. A parameter's name and type decide where it comes from: the path, the query string or the body. Depends() in a signature is an instruction to call someone else's function before yours. async def versus def is the choice between the event loop and the threadpool, made with one keyword. That is why the mistakes here are so quiet: a wrong annotation is not a syntax error but different runtime behaviour, and it shows up under load or in production. The layers below take these mechanisms one at a time — from where FastAPI sits among frameworks to the anatomy of a request and on to what happens after the response.
Topic map
- FastAPI and Django — what the framework gives you — batteries-included against a thin layer, what you take on yourself, and how to choose by the task.
- ASGI, the event loop and Starlette — how ASGI differs from WSGI, who holds the connections, and where middleware fits.
- Path, query and body — how FastAPI decides — the exact binding rule by name and type, and the markers that override it.
- Pydantic models and the 422 — type coercion, when validation runs, and why a client's bad payload is not a server error.
- response_model and response filtering — why it is a filter rather than documentation, and what it cuts on the way out.
- Depends, sub-dependencies and yield — call order, the per-request cache, and the exact moment a resource is released.
- async def versus def — the event loop and the threadpool, the price of a blocking call, and how not to pay it.
- BackgroundTasks and a real queue — what runs in the same process, what is lost on restart, and where the boundary sits.
- JWT authentication — extracting the token, verifying signature and expiry, and why revocation needs a separate decision.
Common mistakes and traps
| Mistake | Consequence |
|---|---|
A synchronous client (requests, a database driver) inside async def | The call holds the worker's single event loop for the whole wait — latency rises on every route, including ones that do nothing, while the CPU sits idle |
Treating response_model as documentation | It genuinely rebuilds the response: fields the model does not declare are dropped. The flip side — a password field that made it into the model reaches the client, and no annotation stops that |
Expecting 422 from a model assembled by hand inside the handler | The built-in handler catches RequestValidationError on the way in only; an exception from the handler body becomes a 500 |
Putting CPU-bound work in BackgroundTasks | The task runs in the same process after the response and holds the interpreter — the worker stops serving requests again |
| Counting on a background task to survive a restart | No queue, no persistence, no retries: when the process is redeployed the task simply disappears |
Using a session from a yield dependency inside a background task | Teardown runs after the response is sent and before background tasks, so the session is already closed there |
Raising HTTPException after yield | The response has already gone to the client — there is nothing left to change, and the exception only reaches the log |
Decoding a JWT without verifying the signature or exp | The token becomes a bearer note: any forged or expired one passes as valid |
| Expecting a single scalar to land in the request body | By the binding rule a scalar type goes to the query string; making it the body requires Body(embed=True) |
response_model on a hot list endpoint | Every row is built twice — first by the ORM, then by the model; on large results that pass dominates the response time |
Why it matters for interviews
FastAPI comes up in almost every middle-level backend interview, and the conversation moves off the framework and onto concurrency fast. It starts harmlessly — "what is FastAPI", "what does Depends do", "why Pydantic" — but the real check follows: "what happens if you call requests.get inside async def". That is where it becomes clear whether the candidate has worked under load. Someone who has once caught a blocked event loop answers with the symptom rather than a definition — latency rising on unrelated routes while the CPU is idle.
The second such indicator is response_model. Answering "it's for the documentation" closes the question against you; what is expected is understanding that the object is rebuilt, and that this is precisely what prevents field leakage. The third is the boundary between BackgroundTasks and a broker-backed queue: the right answer lists what a background task does not have — persistence, retries, process isolation — rather than "it's for light work".
After that come the precision questions. On parameter binding, the interviewer checks whether you state the rule by name and type rather than by argument order. On 422, whether you understand that validation runs before the handler and why that makes the error the client's rather than the server's. The JWT question almost always has a hidden second half — how do you revoke a token; "you cannot, unless you keep a denylist or a short lifetime" scores higher than reciting the OAuth2 flow. The typical failure is the same everywhere: the candidate recites the documentation correctly but cannot name what will actually break under load.