Loading...
Loading...
Backend interview guide
Routes, validation with Pydantic, dependencies, async endpoints and security. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
54 questionsLast updated
Add a handler for Exception that logs the full error with its stack trace on the server and returns a generic 500 message. Never put SQL text, file paths or stack traces in a response. A request id in the body and the log lets support find the case.
Lesson: Error Handling400 bad request for rules the schema cannot express, 401 not logged in, 403 not allowed, 404 not found, 409 conflict such as a duplicate, 422 invalid input (sent by FastAPI), 500 unexpected server error. Pick by what the client can do next.
Lesson: Error HandlingCall the route with TestClient and assert the status code and the whole error body. For the catch-all 500 handler, create TestClient(app, raise_server_exceptions=False), because otherwise the client re-raises the server error in the test.
Lesson: Error HandlingA main file that builds the app and includes routers; a routers folder with one file per topic; services for business rules; repositories for storage; schemas for Pydantic models; a settings module; and a tests folder. Dependencies wire these together so each layer can be replaced in tests.
Lesson: Structuring an App CheckpointA route asks for a service with Depends, and the service asks for a repository and settings. In tests, dependency_overrides replaces the edges with fakes or in-memory versions without touching any production code.
Lesson: Structuring an App CheckpointRead environment variables once into a frozen settings object, give it to code through a dependency, override it in tests, and fail at start-up if production still has default secrets. Keep real secrets out of Git.
Lesson: Structuring an App CheckpointServices and repositories raise domain exceptions. One set of handlers at the app edge maps them to HTTP status codes and one body shape, together with handlers for validation errors and a safe catch-all for unexpected failures.
Lesson: Structuring an App CheckpointAdd it when a file mixes jobs and becomes hard to read or test, for example SQL inside a route. Do not add layers in advance: for a tiny app, a router and a few functions are enough. Structure should solve a problem you actually have.
Lesson: Structuring an App CheckpointService rules with fake repositories, routes with TestClient for success, validation errors and domain errors, settings loading with plain dicts, and a few overrides to show wiring. Aim for many small service tests and fewer route tests.
Lesson: Structuring an App CheckpointA background task is a function that runs after the response has been sent, registered with BackgroundTasks.add_task. Use it for short work the client does not need to wait for, such as sending an email, writing an audit log or clearing a cache entry. The route returns quickly and the client gets its answer sooner.
Tasks run in the same process as the web server. If the process stops, waiting tasks are lost. There is no retry, no schedule and no record of failures. Heavy work can slow the server. So it suits small jobs where an occasional loss is acceptable.
Lesson: Background TasksWhen a job must not be lost, must be retried, must run on a schedule, or is heavy. The app puts the job into a broker such as Redis or RabbitMQ and separate workers run it. Workers can be scaled independently, restarted, and monitored, and failed jobs can be retried.
Lesson: Background TasksNothing different. The response was already sent, so the client keeps its status code. The server logs the error. That is why important tasks should catch their own errors, log them and, if needed, record a failure state.
Lesson: Background TasksUse TestClient. It finishes background tasks before client.post returns, so you call the route and then check the effect of the task, such as a list, a counter or a database row. If the task talks to the outside world, replace that part with a fake.
Return 202 Accepted with a job id at once, run the work as a job and store its state (pending, done, failed). Give the client a status route to poll, and later a link to the result. With one process a background task can do it; for a minute-long job a task queue with workers is safer.
Both work. An async def task is awaited on the event loop. A normal def task is run in a thread pool so it does not block the loop. Do not put blocking calls inside an async task.
Declare a parameter typed UploadFile (optionally with File(...)). FastAPI reads the multipart/form-data body with python-multipart and gives you an object with filename, content_type and async methods such as read. Big files are spooled to a temporary file on disk instead of staying fully in memory.
UploadFile and bytes for a file parameter?bytes = File(...) reads the whole file into memory at once, which is fine only for small files. UploadFile is spooled to disk when large, exposes the file name and content type, and can be read in chunks. For anything more than tiny files, use UploadFile.
Limit the size and answer 413; allow only listed content types and extensions and answer 415; check magic bytes of the content because the type is client-controlled; use a generated file name; store files outside the web root or in object storage; and scan for malware when files are shared with other users.
Lesson: File UploadsIt is an attack where a file name such as ../../app.py makes the server write outside the upload folder. Prevent it by never using the client's name in a path: generate your own name (for example an id plus a safe extension) or at least keep only the last path part.
Not in the same request. A file needs a multipart form body, and JSON needs an application/json body; HTTP allows one body format. Send other fields as Form fields, or send the JSON as a string field and parse it, or use two requests.
Use TestClient and pass files={"file": (name, bytes, content_type)}, with data={...} for form fields. No server or real file is needed. Assert on the status code, the JSON and any side effect such as a saved file in a temporary folder.
Not in the application's own folder. Use object storage such as S3 or a managed disk, save the object key in the database, and serve downloads through signed URLs or a controlled route. That keeps the app stateless so you can run several copies.
Lesson: File UploadsThe application checks the cache first. On a hit it returns the cached value. On a miss it reads the database, stores the result in the cache (usually with a TTL) and returns it. On a write it updates the database and deletes the cached key. The cache is a separate, optional layer, so if it is empty or down, the app still works.
Lesson: Caching with RedisBecause the cached copy and the real data can disagree, and the app must decide when to remove the copy. Data can change from many places, several app copies may hold copies, and a delete can race with a read that fills the cache again. Common tools are short TTLs, delete-on-write and careful key design.
Lesson: Caching with RedisBy how stale the data may be. Prices on a catalogue page might allow minutes. A stock count at checkout should not be cached at all. Short TTLs mean fresher data and more database load; long TTLs the opposite. Add small random jitter so many keys do not expire together.
Lesson: Caching with RedisWhen a popular key expires, many requests miss together and all hit the database. Reduce it with a lock so only one request rebuilds the value, by refreshing a little before expiry, by adding jitter to TTLs, or by serving the old value briefly while one request refreshes it.
Lesson: Caching with RedisAn in-process dict is simplest and fastest, but each app copy has its own, and it is lost on restart. Redis is shared by all copies, survives app restarts, supports TTLs and eviction, and costs a network call. Use Redis once you run more than one process or need consistency between them.
Lesson: Caching with RedisData that must always be exact, such as account balances and stock at checkout; private per-user data under a shared key; and very large or rarely read items. Also do not treat the cache as the only copy of anything: it can be evicted or lost.
Lesson: Caching with RedisPass the clock in. A fake clock with an advance(seconds) method lets a test move time forward instantly, and assertions are stable. Also inject the cache itself (for example as a FastAPI dependency) so a test can use an in-memory cache instead of Redis.
Do the check and the change in one atomic statement: UPDATE products SET stock = stock - :q WHERE id = :id AND stock >= :q, then look at rowcount. If it is 0, the stock was not enough. Another way is SELECT ... FOR UPDATE (with_for_update()) to lock the row inside a transaction, which needs a database such as PostgreSQL.
It opens one session per request, passes it to the route, and closes it in all cases, including errors, because the code after yield runs at the end. Routes stay free of setup code, and tests can replace the dependency with dependency_overrides to use another database.
The model describes storage; the schema describes the API contract. Keeping them apart lets you hide fields such as internal flags or password hashes, change the table without breaking clients, and use different input and output shapes (ProductIn has no id; ProductOut has one).
Binary floats cannot represent most decimals exactly, so sums drift (0.1 + 0.2). Store the smallest unit, such as cents, as an integer, or use Decimal/NUMERIC. Convert to a display string only at the edge.
201 when the order is created, 404 when a product does not exist, 409 when stock is not enough (valid request, conflicting state), and 422 when the body is invalid, which FastAPI sends automatically. Use 400 only for errors that fit nothing more specific.
Lesson: Mini Project: Online Store APIUse SQLite in memory with StaticPool so every request shares one database, create tables at the start, and drive the app with TestClient. Override get_session so each test gets a fresh database. Keep a few tests on the real database type too, because SQLite does not behave like PostgreSQL for locks.
POST /orders after a timeout. How do you avoid a duplicate order?Accept an idempotency key header, store it with the order under a unique constraint, and return the existing order when the same key arrives again. The unique constraint makes the check safe even when two retries arrive together.
Lesson: Mini Project: Online Store APIBackgroundTasks for short, low-risk jobs such as an audit line or a notification, where losing one in a restart is acceptable. A task queue such as Celery or RQ with a broker for jobs that must survive restarts, be retried, run on a schedule or take a long time.
Lesson: Beyond CRUD CheckpointLimit the size (413), allow-list the content types and extensions (415), verify the magic bytes, generate your own stored name to avoid path traversal, keep files outside the code folder or in object storage, and set a body limit in the proxy too.
Lesson: Beyond CRUD CheckpointOn a read, check the cache; on a miss, load from the database, store the value with a TTL, and return it. On a write, update the database and delete the key. For a product page: product:42 is cached for 60 seconds and deleted when the product is edited.
Inject a clock. Production passes time.monotonic; tests pass a fake clock object with an advance method. Tests then run instantly and deterministically, with no sleeping.
Cached data can be stale, shared by mistake between users, or missing an invalidation path when data changes from another place. Reduce the risk with short TTLs, delete-on-write, keys that include everything the answer depends on (such as the user id) and by not caching data that must be exact.
Lesson: Beyond CRUD CheckpointA server only has to check a password, never read it back. A hash is one-way, so a leaked table does not directly give the passwords. Encrypted passwords can be decrypted with the key, and the key is often stored close to the data.
Lesson: Password HashingA salt is a random value, unique per password, mixed in before hashing. It is stored beside the hash and is not secret. It makes equal passwords hash differently and stops attackers from using pre-built tables of common passwords.
Lesson: Password HashingThey are designed to be fast, so an attacker can test billions of guesses per second. Password hashes such as argon2, bcrypt and scrypt are slow and memory-hungry by design, so every guess is costly.
Lesson: Password HashingAll are slow password hashes. PBKDF2 and bcrypt cost CPU time. scrypt and argon2 also cost memory, which hurts attackers using graphics cards. Argon2 is the current first choice, bcrypt is widely supported and PBKDF2 is acceptable with a high iteration count.
Lesson: Password HashingNormal comparison can return as soon as one byte differs, and the timing can leak how many bytes matched. compare_digest takes the same time in both cases. Use it for hashes, tokens and signatures.
Lesson: Password HashingStore the scheme and cost with each hash. At login, after the password is verified, check whether the cost is below the current setting. If so, hash the plain password again with the new settings and save it.
Lesson: Password HashingAssume attackers will try to crack the hashes offline. Force a password reset for every user, invalidate sessions and tokens, rotate secrets, and tell users to change reused passwords elsewhere. Good hashing slows the attack but does not remove the need to react.
Lesson: Password HashingA pepper is a secret value added to every password hash and kept outside the database, for example in a secrets manager. It helps if only the database leaks. It is an extra layer, never a replacement for salts and slow hashing.
Lesson: Password HashingThe client posts username and password as a form to a /token route. The server checks them and returns an access token. For later requests, the client sends the token in the Authorization: Bearer header. The server looks the token up and finds the user, so the password travels only once.
Lesson: OAuth2 Password FlowIt is a dependency that reads the bearer token from the Authorization header and returns 401 if it is missing. It also adds the login button to the docs. It does not create tokens or verify them. Your own code must check the token.
Lesson: OAuth2 Password FlowThe OAuth2 standard defines the token request as application/x-www-form-urlencoded with fields username and password. FastAPI follows this so that the docs page and standard clients work. FastAPI parses it with OAuth2PasswordRequestForm.
Lesson: OAuth2 Password Flow401 means the caller is not authenticated: no token or a bad token. The reply should carry WWW-Authenticate: Bearer. 403 means the caller is known but is not allowed to do this. That is an authorisation decision made after login.
Lesson: OAuth2 Password FlowEach lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson