Beginner FastAPI Interview Questions and Answers

πŸ“‹ Table of Contents β–Ύ
  1. Questions & Answers
  2. 📝 Knowledge Check

⚡ Beginner FastAPI Interview Questions

This lesson covers the fundamental FastAPI concepts every Python backend developer must know. Master routing, path parameters, query parameters, request bodies, Pydantic models, response models, status codes, and automatic documentation. These questions mirror what interviewers ask at junior and entry-level FastAPI roles.

Questions & Answers

01 What is FastAPI and what makes it stand out from Flask and Django?

Core FastAPI is a modern, high-performance Python web framework for building APIs, created by SebastiΓ‘n RamΓ­rez and first released in 2018. It is built on Starlette (ASGI framework) and Pydantic (data validation).

Key advantages over Flask and Django:

  • Performance β€” one of the fastest Python frameworks; on par with NodeJS and Go for I/O-bound workloads thanks to full async/await support
  • Automatic documentation β€” generates interactive Swagger UI (/docs) and ReDoc (/redoc) from your code with zero extra work
  • Type safety β€” uses Python type hints throughout; Pydantic validates all inputs at runtime and generates JSON schemas
  • Native async support β€” designed for async from the ground up (ASGI); no async bolted on later like Flask
  • Less boilerplate β€” 40-90% less code than Flask or Django REST Framework for the same endpoint
  • Editor support β€” type hints enable full autocompletion and inline error detection
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int, active: bool = True):
    return {"user_id": user_id, "active": active}
# Auto-generates: docs, validation, serialisation, error responses

02 How do you create a basic FastAPI application and run it?

Core

# Install
pip install fastapi uvicorn[standard]
# main.py
from fastapi import FastAPI

app = FastAPI(
    title="My API",
    description="API description shown in docs",
    version="1.0.0",
    docs_url="/docs",        # Swagger UI (default)
    redoc_url="/redoc",      # ReDoc (default)
    openapi_url="/openapi.json"
)

@app.get("/")
async def root():
    return {"message": "Hello World"}

@app.get("/health")
def health_check():          # sync functions also work
    return {"status": "ok"}
# Run with uvicorn (ASGI server)
uvicorn main:app --reload          # development: auto-reload on file changes
uvicorn main:app --host 0.0.0.0 --port 8000  # production: bind all interfaces
uvicorn main:app --workers 4       # multiple worker processes

# Or run programmatically
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

Visit http://127.0.0.1:8000/docs for the interactive Swagger UI. FastAPI auto-generates this from your route definitions and type annotations β€” no extra configuration needed.

03 What are path parameters in FastAPI? How do you validate them?

Routing Path parameters are dynamic segments in the URL path declared with curly braces. FastAPI automatically extracts them, converts them to the declared type, and validates them.

from fastapi import FastAPI, Path
from enum import Enum

app = FastAPI()

# Basic path parameter β€” type conversion is automatic
@app.get("/users/{user_id}")
async def get_user(user_id: int):   # "123" in URL β†’ int 123
    return {"user_id": user_id}

# Multiple path parameters
@app.get("/users/{user_id}/orders/{order_id}")
async def get_order(user_id: int, order_id: int):
    return {"user_id": user_id, "order_id": order_id}

# Validation with Path()
@app.get("/items/{item_id}")
async def get_item(
    item_id: int = Path(
        ...,            # ... = required
        title="Item ID",
        description="The ID of the item to retrieve",
        ge=1,           # greater than or equal to 1
        le=9999         # less than or equal to 9999
    )
):
    return {"item_id": item_id}

# Enum path parameter β€” constrain to specific values
class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet  = "resnet"
    lenet   = "lenet"

@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    return {"model": model_name, "value": model_name.value}

04 What are query parameters in FastAPI? How do optional query parameters work?

Routing Query parameters are key-value pairs after the ? in the URL. In FastAPI, function parameters not declared as path parameters are automatically treated as query parameters.

from fastapi import FastAPI, Query
from typing import Optional

app = FastAPI()

# Required query parameter (no default value)
@app.get("/search")
async def search(q: str):
    return {"query": q}
# GET /search?q=python

# Optional query parameter (has a default)
@app.get("/items")
async def list_items(
    skip: int = 0,
    limit: int = 10,
    active: bool = True
):
    return {"skip": skip, "limit": limit, "active": active}
# GET /items?skip=20&limit=5&active=false

# Optional with None default
@app.get("/users")
async def list_users(name: Optional[str] = None):
    if name:
        return {"filter": name}
    return {"filter": "all"}

# Validation with Query()
@app.get("/products")
async def list_products(
    q: Optional[str] = Query(None, min_length=3, max_length=50),
    page: int = Query(1, ge=1),
    size: int = Query(20, ge=1, le=100),
    tags: list[str] = Query(default=[])  # multi-value: ?tags=a&tags=b
):
    return {"q": q, "page": page, "size": size, "tags": tags}

05 What is Pydantic and how does it work with FastAPI?

Pydantic Pydantic is a Python data validation library that uses type hints to define data schemas. FastAPI uses Pydantic models to validate request bodies, generate JSON schemas, and serialise response data.

from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime

class UserCreate(BaseModel):
    name: str = Field(..., min_length=2, max_length=100, example="Alice Johnson")
    email: EmailStr                         # validates email format
    age: Optional[int] = Field(None, ge=0, le=150)
    is_active: bool = True
    tags: list[str] = []

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    created_at: datetime

    class Config:
        from_attributes = True  # allows ORM objects (Pydantic v2: model_config)

# FastAPI automatically parses and validates the JSON body
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
    # user.name, user.email are guaranteed valid Python objects
    # If validation fails, FastAPI automatically returns 422 with error details
    new_user = db.create(user.model_dump())
    return new_user

# Nested models
class Address(BaseModel):
    street: str
    city:   str
    country: str = "UK"

class Customer(BaseModel):
    name:    str
    address: Address            # nested Pydantic model
    orders:  list[int] = []

06 What is a request body in FastAPI? How do you handle JSON payloads?

Request A request body carries data sent by the client in POST, PUT, or PATCH requests. FastAPI reads JSON request bodies and validates them against Pydantic models automatically.

from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

class Item(BaseModel):
    name:        str
    description: Optional[str] = None
    price:       float
    tax:         Optional[float] = None

# Request body β€” FastAPI reads the JSON body and parses it into Item
@app.post("/items")
async def create_item(item: Item):
    item_dict = item.model_dump()
    if item.tax:
        item_dict["price_with_tax"] = item.price + item.tax
    return item_dict

# Mix path + query + body in the same endpoint
@app.put("/items/{item_id}")
async def update_item(
    item_id: int,         # path parameter
    q:       Optional[str] = None,  # query parameter
    item:    Optional[Item] = None  # optional body
):
    result = {"item_id": item_id}
    if q:    result["q"] = q
    if item: result.update(item.model_dump())
    return result

# Multiple body parameters
class User(BaseModel):
    name: str

@app.put("/items/{item_id}/assign")
async def assign_item(item_id: int, item: Item, user: User):
    # FastAPI expects: {"item": {...}, "user": {...}}
    return {"item_id": item_id, "item": item, "user": user}

07 What are response models in FastAPI? Why are they important?

Response The response_model parameter on a route decorator specifies what the response should look like. FastAPI uses it to filter output data, validate the response, and generate documentation.

from pydantic import BaseModel
from typing import Optional

class UserIn(BaseModel):
    name:     str
    email:    str
    password: str        # never expose this in responses!

class UserOut(BaseModel):
    id:    int
    name:  str
    email: str
    # password NOT included β€” it's filtered out automatically

@app.post("/users", response_model=UserOut)
async def create_user(user: UserIn):
    # Even if the DB returns a full user object with password hash,
    # FastAPI will ONLY include id, name, email in the response
    return {"id": 1, "name": user.name, "email": user.email, "password": "SHOULD_NOT_APPEAR"}

# response_model_exclude_unset=True β€” omit fields that weren't set (partial updates)
@app.patch("/users/{id}", response_model=UserOut, response_model_exclude_unset=True)
async def patch_user(id: int, user: UserIn):
    return stored_user

# Response model for lists
@app.get("/users", response_model=list[UserOut])
async def list_users():
    return users_from_db

# Exclude specific fields from response
@app.get("/users/{id}", response_model=UserOut, response_model_exclude={"email"})
async def get_user(id: int):
    return user

08 What HTTP status codes does FastAPI support? How do you return custom status codes?

HTTP FastAPI provides the status module with named constants and lets you set status codes at the route level or raise them inside handlers.

from fastapi import FastAPI, status, HTTPException, Response
from fastapi.responses import JSONResponse

app = FastAPI()

# Set default status code for the route (shown in docs)
@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
    return item

# Raise HTTPException for error responses
@app.get("/items/{item_id}")
async def get_item(item_id: int):
    item = db.get(item_id)
    if not item:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Item {item_id} not found",
            headers={"X-Error": "Item missing"}
        )
    return item

# Return a Response directly (bypasses response_model serialisation)
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    db.delete(item_id)
    return Response(status_code=204)

# Dynamic status codes using Response parameter
@app.post("/items")
async def create_item(item: Item, response: Response):
    if db.exists(item.name):
        response.status_code = status.HTTP_200_OK
        return {"message": "already exists"}
    response.status_code = status.HTTP_201_CREATED
    return db.create(item)

09 What is automatic API documentation in FastAPI?

Docs FastAPI automatically generates OpenAPI 3.x documentation from your route definitions, type hints, and Pydantic models. No annotations or YAML files needed.

  • /docs β€” Swagger UI: interactive browser-based API explorer. Try requests directly in the browser.
  • /redoc β€” ReDoc: clean, readable reference documentation.
  • /openapi.json β€” the raw OpenAPI schema (JSON). Used by code generators, testing tools, and API gateways.
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(title="Product API", version="2.0")

class Product(BaseModel):
    """A product in the catalogue."""
    name:  str
    price: float

@app.post(
    "/products",
    summary="Create a new product",
    description="Creates a product and returns it with its generated ID.",
    response_description="The created product",
    tags=["products"],     # group in docs
    status_code=201
)
async def create_product(product: Product):
    """
    Create a product with:
    - **name**: product name (required)
    - **price**: unit price in GBP (required, must be positive)
    """
    return {"id": 1, **product.model_dump()}

# Tags group endpoints in the docs sidebar
@app.get("/products", tags=["products"])
async def list_products(): ...

@app.get("/orders", tags=["orders"])
async def list_orders(): ...

10 How do you handle request headers and cookies in FastAPI?

Request

from fastapi import FastAPI, Header, Cookie
from typing import Optional

app = FastAPI()

# Headers β€” FastAPI automatically converts hyphenated HTTP headers to underscored Python names
# X-Token β†’ x_token, User-Agent β†’ user_agent
@app.get("/items")
async def read_items(
    x_token: Optional[str] = Header(None),         # X-Token header
    user_agent: Optional[str] = Header(None),       # User-Agent header
    accept_language: Optional[str] = Header(None)   # Accept-Language header
):
    return {"X-Token": x_token, "User-Agent": user_agent}

# Multiple values for the same header
@app.get("/multi-headers")
async def multi_header(x_token: list[str] = Header(default=[])):
    return {"X-Tokens": x_token}

# Cookies
@app.get("/profile")
async def get_profile(
    session_id: Optional[str] = Cookie(None),   # reads "session_id" cookie
    user_pref:  Optional[str] = Cookie(None)
):
    return {"session": session_id}

# Setting cookies in responses
from fastapi.responses import JSONResponse

@app.post("/login")
async def login(response: JSONResponse):
    response = JSONResponse(content={"message": "logged in"})
    response.set_cookie(
        key="session_id",
        value="abc123",
        httponly=True,
        secure=True,
        samesite="lax",
        max_age=3600
    )
    return response

11 What is the difference between async def and def route handlers in FastAPI?

Async

  • async def β€” runs directly in the async event loop. Use when your function calls await (async database calls, HTTP clients, file I/O). Does NOT block the event loop.
  • def β€” FastAPI automatically runs sync functions in a thread pool executor to avoid blocking the event loop. Safe to use for CPU-bound code or sync libraries.
import asyncio
import httpx

# async def β€” for async operations (await)
@app.get("/users/{id}")
async def get_user(id: int):
    async with httpx.AsyncClient() as client:        # async HTTP call
        response = await client.get(f"https://api.example.com/users/{id}")
    return response.json()

# def β€” for sync/CPU-bound code (FastAPI runs it in a thread pool)
@app.get("/compute")
def heavy_computation():
    import time
    time.sleep(2)        # blocks the thread, NOT the event loop
    return {"result": 42}

# ❌ WRONG β€” blocking call inside async def blocks the event loop
@app.get("/bad")
async def bad_endpoint():
    import time
    time.sleep(2)        # BLOCKS entire event loop β€” all requests hang!
    return {"result": "slow"}

# βœ… CORRECT β€” run blocking code in thread pool from async def
import asyncio
@app.get("/good")
async def good_endpoint():
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(None, blocking_function)
    return {"result": result}

12 How do you handle file uploads in FastAPI?

Files

from fastapi import FastAPI, File, UploadFile, Form
from typing import Optional
import aiofiles

app = FastAPI()

# UploadFile β€” recommended (uses SpooledTemporaryFile, memory-efficient)
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    contents = await file.read()          # read entire file into memory
    return {
        "filename":     file.filename,
        "content_type": file.content_type,
        "size":         len(contents)
    }

# Save file to disk efficiently (streaming, no full memory load)
@app.post("/upload/save")
async def save_file(file: UploadFile = File(...)):
    async with aiofiles.open(f"uploads/{file.filename}", "wb") as f:
        while chunk := await file.read(1024 * 64):  # 64KB chunks
            await f.write(chunk)
    return {"saved": file.filename}

# Multiple files
@app.post("/upload/multiple")
async def upload_multiple(files: list[UploadFile] = File(...)):
    return [{"filename": f.filename, "size": f.size} for f in files]

# File + form fields together
@app.post("/upload/with-metadata")
async def upload_with_metadata(
    file:        UploadFile = File(...),
    title:       str        = Form(...),
    description: Optional[str] = Form(None)
):
    return {"title": title, "description": description, "filename": file.filename}

13 What is the FastAPI Router (APIRouter)? How do you organise routes?

Routing APIRouter is FastAPI’s equivalent of Flask Blueprints or Express Router β€” it lets you organise routes into separate files and modules.

# routers/users.py
from fastapi import APIRouter, Depends, HTTPException

router = APIRouter(
    prefix="/users",              # all routes start with /users
    tags=["users"],               # grouped in docs
    responses={404: {"description": "Not found"}}
)

@router.get("/")
async def list_users():
    return [{"id": 1, "name": "Alice"}]

@router.get("/{user_id}")
async def get_user(user_id: int):
    return {"id": user_id, "name": "Alice"}

@router.post("/", status_code=201)
async def create_user(user: UserCreate):
    return {"id": 2, **user.model_dump()}

# routers/orders.py
router = APIRouter(prefix="/orders", tags=["orders"])
# ... order routes

# main.py β€” mount all routers
from fastapi import FastAPI
from routers import users, orders

app = FastAPI()

app.include_router(users.router)
app.include_router(orders.router)

# With additional prefix (e.g., API versioning)
app.include_router(users.router, prefix="/api/v1")
app.include_router(
    admin.router,
    prefix="/admin",
    dependencies=[Depends(verify_admin_token)]  # apply auth to all admin routes
)

14 What are HTTP exceptions in FastAPI? How do you create custom exceptions?

Error Handling

from fastapi import FastAPI, HTTPException, Request, status
from fastapi.responses import JSONResponse
from fastapi.exception_handlers import http_exception_handler

app = FastAPI()

# Standard HTTPException
@app.get("/items/{item_id}")
async def get_item(item_id: int):
    if item_id not in db:
        raise HTTPException(
            status_code=404,
            detail={"message": f"Item {item_id} not found", "item_id": item_id}
        )
    return db[item_id]

# Custom exception class
class ItemNotFoundError(Exception):
    def __init__(self, item_id: int):
        self.item_id = item_id

# Custom exception handler
@app.exception_handler(ItemNotFoundError)
async def item_not_found_handler(request: Request, exc: ItemNotFoundError):
    return JSONResponse(
        status_code=404,
        content={"error": "ITEM_NOT_FOUND", "item_id": exc.item_id,
                 "message": f"Item {exc.item_id} does not exist"}
    )

# Use the custom exception
@app.get("/items/{item_id}")
async def get_item(item_id: int):
    item = db.get(item_id)
    if not item:
        raise ItemNotFoundError(item_id=item_id)
    return item

# Override default validation error handler (422 responses)
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=422,
        content={"detail": exc.errors(), "body": str(exc.body)}
    )

15 What is the Request object in FastAPI? When do you use it directly?

Request The Request object gives direct access to the raw HTTP request β€” useful when you need information not covered by the standard parameters.

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/info")
async def request_info(request: Request):
    return {
        "method":       request.method,
        "url":          str(request.url),
        "path":         request.url.path,
        "query_params": dict(request.query_params),
        "headers":      dict(request.headers),
        "client_ip":    request.client.host,
        "client_port":  request.client.port
    }

# Access raw body (e.g., for webhooks with custom signatures)
@app.post("/webhook")
async def handle_webhook(request: Request):
    raw_body = await request.body()        # bytes
    json_body = await request.json()       # dict (parses JSON)
    form_data = await request.form()       # form fields
    return {"received": len(raw_body)}

# Store data on request.state (pass data between middleware and handlers)
# (Middleware sets it, handler reads it)
@app.get("/protected")
async def protected_route(request: Request):
    user = request.state.user    # set by auth middleware
    return {"user": user.name}

16 What are response types in FastAPI?

Response FastAPI supports multiple response types beyond JSON for returning HTML, files, streams, and redirects.

from fastapi import FastAPI
from fastapi.responses import (
    JSONResponse,       # default β€” returns JSON
    HTMLResponse,       # HTML content
    PlainTextResponse,  # plain text
    RedirectResponse,   # HTTP redirect
    FileResponse,       # serve a file from disk
    StreamingResponse,  # stream large data
    Response            # raw response (full control)
)

app = FastAPI()

@app.get("/html", response_class=HTMLResponse)
async def get_html():
    return "<h1>Hello World</h1>"

@app.get("/redirect")
async def redirect():
    return RedirectResponse(url="/new-location", status_code=302)

@app.get("/file")
async def download_file():
    return FileResponse(
        path="reports/annual.pdf",
        filename="annual-report.pdf",
        media_type="application/pdf"
    )

# Streaming large files or generated content
import asyncio
async def fake_video_streamer():
    for i in range(10):
        yield b"video chunk data"
        await asyncio.sleep(0.1)

@app.get("/stream")
async def stream_video():
    return StreamingResponse(fake_video_streamer(), media_type="video/mp4")

# Custom JSON with non-serialisable types (datetime, Decimal, UUID)
from fastapi.encoders import jsonable_encoder
@app.get("/custom")
async def custom_response():
    data = {"date": datetime.now(), "id": uuid4()}
    return JSONResponse(content=jsonable_encoder(data))

17 What is Pydantic v2 and how does it differ from v1?

Pydantic Pydantic v2 (released 2023, default in FastAPI 0.100+) is a complete rewrite in Rust β€” significantly faster and with a cleaner API.

# Pydantic v1 β†’ v2 migration guide

# Model definition β€” mostly the same syntax
from pydantic import BaseModel, Field, field_validator

class User(BaseModel):
    name: str
    age:  int

# v1: .dict()          β†’ v2: .model_dump()
user.model_dump()
user.model_dump(exclude_unset=True)  # only set fields
user.model_dump(include={"name"})    # specific fields

# v1: .json()          β†’ v2: .model_dump_json()
user.model_dump_json()

# v1: .from_orm()      β†’ v2: model_validate() + model_config
from pydantic import ConfigDict
class UserORM(BaseModel):
    model_config = ConfigDict(from_attributes=True)  # v2
    # v1: class Config: orm_mode = True

# v1: @validator         β†’ v2: @field_validator
from pydantic import field_validator, model_validator

class Product(BaseModel):
    name:  str
    price: float

    @field_validator("price")
    @classmethod
    def price_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError("Price must be positive")
        return v

    @model_validator(mode="after")   # cross-field validation
    def check_name_price(self):
        if "free" in self.name.lower() and self.price > 0:
            raise ValueError("Free items must have price 0")
        return self

18 How do you add CORS support to a FastAPI application?

Middleware CORS (Cross-Origin Resource Sharing) is required when your frontend JavaScript app (running on a different origin) calls your FastAPI backend. FastAPI provides built-in CORS middleware via Starlette.

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Allow specific origins (production)
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://myapp.com",
        "https://admin.myapp.com"
    ],
    allow_credentials=True,       # allow cookies and auth headers
    allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
    allow_headers=["Authorization", "Content-Type", "X-Request-ID"],
    expose_headers=["X-Total-Count"],  # expose to JS
    max_age=86400                 # preflight cache: 24 hours
)

# Allow all origins (development only β€” NOT for production)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"]
)

# Dynamic origins from environment
import os
origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
app.add_middleware(CORSMiddleware, allow_origins=origins)

19 What are Form fields in FastAPI? How do you handle HTML form submissions?

Forms Form fields handle application/x-www-form-urlencoded and multipart/form-data requests β€” used for traditional HTML forms and OAuth2 flows.

from fastapi import FastAPI, Form
from typing import Optional

app = FastAPI()

# HTML form login (application/x-www-form-urlencoded)
@app.post("/login")
async def login(
    username: str = Form(...),
    password: str = Form(...)
):
    if check_credentials(username, password):
        return {"access_token": create_token(username)}
    raise HTTPException(status_code=401, detail="Invalid credentials")

# Form + file upload (multipart/form-data)
@app.post("/upload-profile")
async def upload_profile(
    name:   str        = Form(...),
    bio:    Optional[str] = Form(None),
    avatar: UploadFile = File(...)
):
    return {"name": name, "bio": bio, "avatar": avatar.filename}

# NOTE: You cannot mix Pydantic request body + Form fields in the same endpoint
# The body must be EITHER JSON (Pydantic model) OR form data (Form/File fields)
# This is an HTTP protocol constraint, not a FastAPI limitation

# OAuth2 password flow uses form fields
@app.post("/token")
async def get_token(
    grant_type: str = Form(...),
    username:   str = Form(...),
    password:   str = Form(...)
):
    token = authenticate_user(username, password)
    return {"access_token": token, "token_type": "bearer"}

20 How do you use background tasks in FastAPI?

Async Background tasks run after a response is sent β€” useful for sending emails, processing data, or updating logs without making the client wait.

from fastapi import FastAPI, BackgroundTasks
import smtplib, time

app = FastAPI()

def send_welcome_email(email: str, name: str):
    """Runs in background after response is sent."""
    time.sleep(3)  # simulate sending email
    print(f"Welcome email sent to {email}")

def log_activity(user_id: int, action: str):
    """Log to analytics service."""
    analytics.track(user_id, action)

@app.post("/users", status_code=201)
async def create_user(user: UserCreate, background_tasks: BackgroundTasks):
    new_user = db.create(user)

    # Add tasks β€” they run AFTER the response is returned to the client
    background_tasks.add_task(send_welcome_email, user.email, user.name)
    background_tasks.add_task(log_activity, new_user.id, "user_created")

    return new_user  # client gets this immediately

# Inject BackgroundTasks into dependencies too
@app.post("/orders")
async def create_order(
    order:            OrderCreate,
    background_tasks: BackgroundTasks
):
    new_order = db.create_order(order)
    background_tasks.add_task(notify_warehouse, new_order.id)
    background_tasks.add_task(send_confirmation_email, order.email, new_order)
    return new_order

Limitation: BackgroundTasks run in the same process. For heavy or distributed workloads, use a proper task queue like Celery + Redis/RabbitMQ or ARQ.

21 What are the main HTTP methods used in REST APIs with FastAPI?

REST

app = FastAPI()

# GET β€” retrieve a resource (idempotent, safe)
@app.get("/products")           # list all
@app.get("/products/{id}")      # get one

# POST β€” create a new resource (not idempotent)
@app.post("/products")          # create

# PUT β€” replace entire resource (idempotent)
@app.put("/products/{id}")      # full update/replace

# PATCH β€” partial update (idempotent if well-designed)
@app.patch("/products/{id}")    # partial update

# DELETE β€” remove a resource (idempotent)
@app.delete("/products/{id}")   # delete

# HEAD β€” like GET but no response body (check if resource exists)
@app.head("/products/{id}")

# OPTIONS β€” returns allowed methods (CORS preflight)
@app.options("/products")

# Practical example of a full CRUD router
@app.get("/products",               response_model=list[ProductOut])
@app.get("/products/{id}",          response_model=ProductOut)
@app.post("/products",              response_model=ProductOut, status_code=201)
@app.put("/products/{id}",          response_model=ProductOut)
@app.patch("/products/{id}",        response_model=ProductOut)
@app.delete("/products/{id}",       status_code=204)

22 What is the lifespan event in FastAPI? How do you handle startup and shutdown?

Lifecycle The lifespan context manager (FastAPI 0.93+, replaces old on_event) runs code on application startup and shutdown β€” used for connecting to databases, loading ML models, and cleaning up resources.

from fastapi import FastAPI
from contextlib import asynccontextmanager
from motor.motor_asyncio import AsyncIOMotorClient

# Lifespan context manager (modern approach)
@asynccontextmanager
async def lifespan(app: FastAPI):
    # ── STARTUP β€” runs before the app accepts requests ──
    print("Starting up...")
    app.state.db = AsyncIOMotorClient(MONGO_URI)
    app.state.redis = await aioredis.from_url(REDIS_URL)
    app.state.ml_model = load_model("model.pkl")
    print("Ready to accept requests")

    yield  # application runs here

    # ── SHUTDOWN β€” runs after last request is complete ──
    print("Shutting down...")
    app.state.db.close()
    await app.state.redis.close()
    print("Cleanup complete")

app = FastAPI(lifespan=lifespan)

# Access shared resources in route handlers
@app.get("/users")
async def list_users(request: Request):
    db = request.app.state.db
    users = await db.mydb.users.find().to_list(100)
    return users

📝 Knowledge Check

Test your understanding of FastAPI fundamentals with these five questions.

🧠 Quiz Question 1 of 5

What happens automatically when you declare a function parameter with a type hint in a FastAPI route?





🧠 Quiz Question 2 of 5

What is the purpose of the response_model parameter on a FastAPI route decorator?





🧠 Quiz Question 3 of 5

What is the risk of using a blocking operation like time.sleep() inside an async def route handler in FastAPI?





🧠 Quiz Question 4 of 5

Where does FastAPI automatically generate interactive API documentation?





🧠 Quiz Question 5 of 5

What does BackgroundTasks do in FastAPI?