1
What Is FastAPI
A modern Python web framework built for speed and type safety.

FastAPI is a Python web framework for building APIs. It's built on top of Starlette (ASGI web toolkit) and Pydantic (data validation via Python type hints).

Key things it gives you out of the box:

- request/response validation from type hints - automatic interactive docs (Swagger UI + ReDoc) - async support (native async def endpoints) - dependency injection system - high performance (ASGI, comparable to Node/Go frameworks)

You describe your data shapes once as Python classes, and FastAPI handles parsing, validation, serialization, and documentation from that single source of truth.

2
How FastAPI Works Under the Hood
The pieces that actually handle a request, end to end.

FastAPI itself doesn't run a server or handle raw sockets — it sits on top of a small stack, and each layer has one job:

Uvicorn

An ASGI server. It accepts raw HTTP/WebSocket connections and turns them into Python calls. ASGI (Asynchronous Server Gateway Interface) is the async successor to WSGI — it's what makes native async def endpoints possible.

Starlette

The underlying web toolkit FastAPI is built on. It provides routing, middleware, background tasks, and the request/response objects. FastAPI adds validation and docs generation on top of it.

Pydantic

Handles data validation and serialization. Your type hints (item_id: int, a BaseModel subclass) aren't just documentation — Pydantic actually parses and validates against them at runtime.

Request flow:

Client → Uvicorn (ASGI server: accepts the connection) → Starlette (routing: matches the URL to your function) → FastAPI (validation: parses path/query/body via Pydantic) → your endpoint function runs → return value serialized back to JSON
Interview line: Uvicorn runs it, Starlette routes it, Pydantic validates it — FastAPI is the layer that wires the three together around your type hints.
3
Project Setup
Virtual environment and installing dependencies.

Create an isolated environment, then install FastAPI and an ASGI server (Uvicorn):

python3 -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install fastapi "uvicorn[standard]" pip freeze > requirements.txt

Minimal recommended project layout for a single-file app:

fastapi/ ├── main.py └── requirements.txt
Note: uvicorn[standard] pulls in extras like uvloop and httptools for better performance — plain uvicorn also works for learning/dev.
4
Minimal App
The smallest working FastAPI application.
from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"message": "Hello, FastAPI"}

app is the core application object. Every route is registered on it using a decorator that matches the HTTP method: @app.get, @app.post, @app.put, @app.delete, etc.

Returning a Python dict is enough — FastAPI serializes it to JSON automatically.

5
Running the App
Starting the dev server and finding the auto-generated docs.
uvicorn main:app --reload

main:app means "in main.py, use the object named app". --reload restarts the server on code changes — dev only, not for production.

Once running, FastAPI gives you free interactive docs:

http://127.0.0.1:8000/ → your API http://127.0.0.1:8000/docs → Swagger UI (try requests in-browser) http://127.0.0.1:8000/redoc → ReDoc (read-only reference view)
6
Async vs Sync Endpoints
When def is fine and when you need async def.

FastAPI lets you write either def or async def for the same endpoint, and both work — but they behave differently under load.

@app.get("/sync") def sync_endpoint(): return {"type": "sync"} @app.get("/async") async def async_endpoint(): return {"type": "async"}

A regular def endpoint runs in a worker thread pool, so it won't block the main event loop even if it does blocking work. An async def endpoint runs directly on the event loop — which is efficient only if everything it awaits is also async.

The trap: calling a blocking library (e.g. a sync DB driver, requests) inside an async def function blocks the entire event loop — every other request on that worker stalls too.

Rule of thumb: - CPU-light + all-async I/O (async DB driver, httpx.AsyncClient) → async def - Any blocking/sync call inside → plain def (FastAPI threads it for you)
Interview line: async def isn't automatically faster — it's only faster when every I/O call inside it is also non-blocking.
7
Path Parameters
Values embedded in the URL path itself.
@app.get("/items/{item_id}") def read_item(item_id: int): return {"item_id": item_id}

The type hint (item_id: int) is enforced automatically. /items/abc returns a 422 Unprocessable Entity with a clear validation error — no manual parsing or checks needed.

8
Query Parameters
Function parameters not in the path become query params.
@app.get("/items/") def list_items(skip: int = 0, limit: int = 10, q: str | None = None): return {"skip": skip, "limit": limit, "q": q}

Call it as /items/?skip=0&limit=5&q=phone. Parameters with a default value are optional; those without one are required.

9
Request Body (Pydantic)
Defining and validating JSON payloads.
from pydantic import BaseModel class Item(BaseModel): name: str price: float in_stock: bool = True @app.post("/items/") def create_item(item: Item): return {"received": item}

A BaseModel subclass declares the expected JSON shape. FastAPI parses the request body into an Item instance, validates every field, and rejects malformed requests before your function even runs.

10
Response Model
Controlling and documenting exactly what a route returns.
class ItemOut(BaseModel): name: str price: float @app.post("/items/", response_model=ItemOut) def create_item(item: Item): return item

response_model filters and shapes the output — useful for hiding internal-only fields (like a stored ID or a hashed password) even if the object you return has more attributes than the model declares.

Interview line: FastAPI validates input against the request-body model, and output against response_model — they can be different classes.
11
Dependency Injection
Sharing setup logic — auth, DB sessions, config — across routes.

A dependency is just a function FastAPI calls for you before your endpoint runs, and whose return value it passes in as an argument. It's how FastAPI avoids repeating the same setup code (DB session, current user, pagination defaults) in every route.

def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.get("/users/{user_id}") def get_user(user_id: int, db=Depends(get_db)): return db.query(User).get(user_id)

Depends(get_db) tells FastAPI: "run get_db, and give me whatever it yields." The same dependency can be reused across dozens of routes, and it's swappable in tests — you override get_db with a fake one instead of touching a real database.

A dependency can itself depend on other dependencies, which is how auth is usually layered in:

def get_current_user(token: str = Depends(oauth2_scheme)): user = decode_token(token) if not user: raise HTTPException(status_code=401, detail="Invalid token") return user @app.get("/me") def read_me(user=Depends(get_current_user)): return user
Interview line: Dependency injection in FastAPI is just "run this function first and hand me its result" — used for DB sessions, auth, and shared validation without repeating code.
12
Error Handling & Status Codes
Returning the right HTTP status when something goes wrong.

Raise HTTPException to stop execution and return a specific status code with a JSON error body:

from fastapi import HTTPException @app.get("/items/{item_id}") def read_item(item_id: int): if item_id not in fake_db: raise HTTPException(status_code=404, detail="Item not found") return fake_db[item_id]

Common status codes you'll actually use:

200 OK → default success 201 Created → successful POST that created a resource 400 Bad Request → malformed input, business-rule violation 401 Unauthorized → missing/invalid credentials 403 Forbidden → authenticated, but not allowed 404 Not Found → resource doesn't exist 422 Unprocessable → FastAPI's own validation failure (automatic) 500 Internal Error → unhandled exception

For errors that aren't tied to one route, register a global handler instead of repeating try/except everywhere:

@app.exception_handler(ValueError) def value_error_handler(request, exc): return JSONResponse(status_code=400, content={"error": str(exc)})
Note: 422 is different from the other codes here — you don't raise it yourself. FastAPI returns it automatically whenever Pydantic validation fails on a path/query/body param.
13
FastAPI vs Flask vs Django
Where FastAPI actually fits among Python's web frameworks.
Flask

WSGI (sync by default). Minimal core, huge ecosystem of extensions. No built-in validation or docs — you add libraries (Marshmallow, Flask-RESTX) for that yourself.

Django

Batteries-included: ORM, admin panel, auth, migrations all ship together. Traditionally sync/WSGI; Django REST Framework is the standard way to build APIs with it. Best fit for large, full-stack apps that want one opinionated way to do things.

FastAPI

ASGI (async-native). No ORM or admin panel bundled — it's an API layer, not a full framework. Its edge is type-hint-driven validation and docs generation, and first-class async support for high-concurrency I/O-bound services.

Interview line: Reach for Django when you need a full batteries-included app (admin, ORM, auth) fast; Flask for a small, unopinionated app; FastAPI when you're building an API-first service and want async performance plus validation/docs for free.
14
Files in This Folder
Where the runnable example lives.
api-design/ ├── notes.html this page ├── main.py runnable example combining every section above └── requirements.txt fastapi + uvicorn

To run the example:

cd api-design python3 -m venv venv && source venv/bin/activate pip install -r requirements.txt uvicorn main:app --reload