Chapter 35, Interviews
Interview questions
The most common questions in interviews about FastAPI and APIs, with the answer I would give, the explanation behind it, and the chapter where each topic is covered.
How to use this chapter
A short answer shows that you memorized, and an answer with the why shows that you understood. In each question, I provide both. Try to answer out loud before reading. The questions follow the order of the course.
API fundamentals
What is an API and what is a route? An API is a contract between the requester and the responder: what addresses exist, with which verbs, with which data, and what comes back. A route is a path (/usuarios) plus a verb (GET), linked to a function that runs when the request arrives. (Chapters 1 and 3.)
What is the difference between GET, POST, PUT, PATCH and DELETE? The GET reads and does not change anything. The POST creates. The PUT replaces an entire resource. The PATCH changes only part of it. The DELETE remove. One detail that impresses: GET, PUT and DELETE are idempotent (repeating the same request leaves the server in the same state), and the POST is normally not (repeating creates another resource). (Chapters 3 and 8.)
What is the difference between path parameter, query parameter, and body? The path identifies the resource (/usuarios/7). The query filters or adjusts (?ativo=true). The body carries the data that will be created or altered. In FastAPI, what is in {} in the route is path, a simple type outside of it is query, and a Pydantic model is body. (Chapters 4, 5, and 9.)
Why does FastAPI return JSON by default? Because the goal of an API is to exchange data between programs, and JSON is lightweight, readable, and understood by practically every language. FastAPI automatically converts dictionaries, lists, and models to JSON. (Chapter 3.)
What is Swagger UI and where does it come from? It is the page /docs, generated from the document OpenAPI that FastAPI builds on its own with your routes and types. It lists the routes and lets you test them. The /redoc shows the same document in another way. (Chapters 1 and 2.)
Why use FastAPI instead of Flask or Django? There is no "best". FastAPI was designed for APIs, with automatic validation and documentation from types and asynchronous support from the start. Django is excellent for complete applications (admin panel, ORM, authentication). Flask is small and flexible. Be wary of anyone who answers "because it's the fastest": performance depends much more on the database and your code than on the framework. (Chapter 1.)
Pydantic, validation, and errors
What is Pydantic and why a model instead of a dictionary? It is the library that validates and converts data from type annotations. A dictionary accepts anything, and you write each check by hand. The model declares the structure once and gains validation, conversion, error messages, and documentation. (Chapters 6 and 7.)
Does Pydantic reject fields that the model does not declare? By default, no: it silently ignores silently. To reject them, configure extra="forbid", which helps catch client typos. (Chapter 7.)
What is the purpose of the response_model? To control what comes out: it filters the function's return by the model, which prevents sensitive fields (like the password) from leaking, and validates the output. That's why I separate the input, output, and database models. (Chapter 10.)
When to use 200, 201, and 204? And 400, 401, 403, 404, 409, and 422? 200 for successful reading or modification, 201 when something was created, 204 when it worked and there is no body (a DELETE). 400 for an invalid request due to business rules, 401 when the client is not authenticated, 403 when authenticated but does not have permission, 404 when the resource does not exist, 409 for conflict with the current state (duplicate email) and 422 when the data fails validation. (Chapter 11.)
Why never return an error with status 200? Because the client looks at the status first. A response 200 with {"error": ...} says "it worked" and the code consuming the API proceeds as if it had worked. (Chapter 11.)
What is the difference between HTTPException, a custom exception and a global handler? The HTTPException interrupts the route and returns a direct HTTP error. A custom exception expresses an error from your domain, without worrying about HTTP. The global handler (@app.exception_handler) translates an exception into a response in one place only, which avoids code repetition and ensures a unique error format. (Chapter 12.)
Dependencies and middleware
What is dependency injection in FastAPI? It is declaring, with Depends, a function that the route requests and FastAPI executes and delivers. It serves to reuse authentication, pagination, and database access, without copying code. The same dependency used twice in the same request runs once. (Chapter 13.)
What is the purpose of the yield in a dependency? To open a resource before the route and close it afterwards, even if an error occurs: the database session pattern. (Chapters 13 and 16.)
What is the difference between middleware and dependency? The middleware crosses every request, on the way there and back, and sees the response: it serves for time, request identifier, and compression. The dependency only applies to the routes that request it and receives validated parameters: it serves for authentication and database. (Chapter 14.)
In what order do the middlewares execute? The the last registered is left out: is the first to see the request and the last to see the response. (Chapter 14.)
Database
What is an ORM, and pure SQL or ORM? It is a layer that maps Python classes to tables and generates SQL for you. Pure SQL is suitable for small scripts, and the ORM protects against SQL injection and allows switching databases, which makes it the choice for an API that will grow. (Chapters 15 and 16.)
What is SQL injection and how to avoid it? It is when user data, constructed within a SQL string, changes the query (x' OR '1'='1 returns all records). It is avoided with parameters (? in the sqlite3) or with an ORM, and never with f-string or concatenation. (Chapter 15.)
Why one session per request? What is the difference between commit and refresh? A shared session between requests mixes transactions and is not safe between threads. The commit commits the changes to the database. The refresh reloads the object with what is in the database, which matters when the database generates values (the creation date). By default, the commit expires the objects, and accessing them with the session already closed gives DetachedInstanceError, which is why one uses expire_on_commit=False or refresh. (Chapters 16 and 17.)
Pagination by page or by cursor? By page (OFFSET) allows jumping to any page, but the cost grows with depth and, if the data changes between two requests, items are skipped or repeated. By cursor ("after item X") has constant cost and is stable, but only advances. In any case, the query needs ORDER BY. (Chapter 27.)
What is a migration and why not use only the create_all? The create_all creates tables that do not exist, and does not alter the existing ones. A migration (Alembic) versions the evolution of the schema and allows adding a column to a table with real data. (Chapter 34.)
Asynchrony
What is the difference between def and async def in a route? The route def runs in a thread pool, and the async def runs event loop. The rule is to match with the library: synchronous libraries require def, and asynchronous libraries require async def with await. (Chapter 18.)
What happens if I call time.sleep (or a synchronous library) inside a route async def? The event loop until that finishes. An example with three routes that "take" 0.2 seconds each, called 5 times at the same time:, and all other requests wait. I measured: five calls of 0.2 seconds took about 1 second, compared to 0.2 seconds with await asyncio.sleep. The output is asyncio.to_thread or a route def. (Chapter 18.)
Security
How to store passwords? How slow hash with salt (Argon2, for example), and never in plain text or with fast hashes like SHA-256. The salt makes the same password generate different hashes, and the slowness makes it expensive to test billions of passwords. (Chapter 19.)
What is a JWT? Is it encrypted? It is a token with three parts (header, body, and signature). It is not encrypted: anyone can read the body by decoding Base64. The signature ensures that the content has not been altered. Therefore, nothing sensitive goes in it. (Chapter 19.)
Why does a token need expiration, and how to revoke it? To limit the damage of a leaked token. A JWT is stateless, so the server cannot cancel it before expiration: that's why the validity is short, and long sessions use a refresh token stored on the server, which can be revoked. (Chapter 20.)
Why does the decode require algorithms=[...]? To prevent the algorithm attack none, where the attacker forges a token without a signature. With the fixed list, the server rejects it. (Chapter 19.)
What is CORS? Is it a server protection? It is a rule of the browser that decides if JavaScript from one origin can read the response from another. It does not protect the server: curl and other servers ignore it. And allow_origins=["*"] with credentials is dangerous: Starlette returns the origin of who asked, which exposes any site with credentials. (Chapter 22.)
How to upload securely? Never use the name that the client sent to decide where to save (a ../ leaves the folder). Generate the name on the server, validate the extension against a list, limit the size during reading, and do not trust the content-type. (Chapter 21.)
How does rate limiting work, and what can go wrong? The token bucket, fixed window, and sliding window limit requests per client over a period, and return 429 with Retry-After. Behind a proxy, the IP seen is that of the proxy, and with multiple processes, each counts its own limit: for a global limit, the counter needs shared storage. (Chapter 29.)
Testing, configuration, and production
How to test a route that uses a database and authentication? With the TestClient and app.dependency_overrides, swapping the session for a test database and the user for a fixed one. Each test should start with the clean database, otherwise the execution order changes the result. (Chapter 24.)
How to handle configuration and secrets? Outside of the code, in environment variables (or a .env that does not goes to Git), read by a typed class (pydantic-settings) that refuses to start if something is missing. (Chapter 23.)
How does caching work and what is the difficult part? A response is stored for a time (TTL), and the difficult part is knowing when it becomes invalid. Cautions: in-memory cache is per process, user data needs the user in the key, and the ETag allows the client to revalidate with a 304. (Chapter 28.)
How to deploy an API? Locked dependencies, secrets in the environment, a health route, logs to standard output, fastapi run (without automatic reload) on the platform port, and multiple processes according to the cores, remembering that the state is not shared among them. (Chapter 30.)
A question that always comes at the end
"What is the decision you regret the most in an API?" There is no right answer, and that’s where the interview turns into a conversation. Mine, frequently: not having limited and ordered the lists from day one. A route that returns "everything" works with 50 records and crashes with 500 thousand.