backend / web frameworks / fastapi / 09_more_interview_questions.md

FastAPI — Common Interview Questions and Answers

4 interview angles 5 min read source

FastAPI — Common Interview Questions and Answers

1. What is FastAPI and what are its main advantages?

FastAPI is a modern, high-performance web framework for building APIs with Python 3.7+, based on standard type hints. It is built on Starlette (ASGI) and Pydantic.

Main advantages:

  • Performance: One of the fastest Python frameworks (comparable to Node.js/Go).
  • Developer experience: Auto-generated OpenAPI (Swagger) docs, editor autocomplete, fewer bugs.
  • Data validation: Automatic request/response validation via Pydantic.
  • Async support: Native async/await for non-blocking I/O.
  • Standards: OpenAPI 3.0 and JSON Schema.

2. How do you install and run a minimal FastAPI app?

pip install fastapi uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

# Run: uvicorn main:app --reload

3. What is Uvicorn and why use it with FastAPI?

Uvicorn is an ASGI server. FastAPI is an ASGI app, so it needs an ASGI server to run. Uvicorn provides:

  • Async request handling
  • WebSocket support
  • Hot reload in development
  • Production-ready workers: uvicorn main:app --workers 4

4. FastAPI vs Flask — main differences?

Aspect FastAPI Flask
Performance High (async) Synchronous
Validation Built-in (Pydantic) Manual / add-ons
Docs Auto OpenAPI/Swagger Manual
Type hints Core part of API Optional
Async Native Via extensions

5. What are path parameters and query parameters? How do you define them?

  • Path parameters: Part of the URL path (e.g. /users/1).
  • Query parameters: After ? (e.g. /items?skip=0&limit=10).
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

@app.get("/items")
def list_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

6. How does dependency injection work in FastAPI?

Dependencies are functions (or classes) whose return value is injected into route parameters via Depends().

from fastapi import Depends, FastAPI

def get_db():
    db = Database()
    try:
        yield db
    finally:
        db.close()

@app.get("/users")
def get_users(db = Depends(get_db)):
    return db.get_users()

Use cases: DB sessions, auth, shared config, validation.


7. How do you handle authentication (e.g. JWT) in FastAPI?

Use a dependency that reads and validates the token (e.g. from Authorization header) and returns the current user; use Depends in routes that require auth.

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer()

def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    user = decode_and_validate_jwt(token)  # your logic
    if not user:
        raise HTTPException(status_code=401, detail="Invalid token")
    return user

@app.get("/me")
def me(current_user = Depends(get_current_user)):
    return current_user

8. What are FastAPI request body models and how are they used?

Request body models are Pydantic models used as type hints for the body. FastAPI validates the JSON body and parses it into the model.

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = None

@app.post("/items")
def create_item(item: Item):
    return {"name": item.name, "price": item.price}

9. How do you return different response status codes and raise errors?

Use Response with status_code or raise HTTPException:

from fastapi import HTTPException
from fastapi.responses import JSONResponse

@app.post("/items")
def create_item(item: Item):
    if item.price < 0:
        raise HTTPException(status_code=400, detail="Price must be positive")
    return item

@app.delete("/items/{id}")
def delete_item(id: int):
    return JSONResponse(status_code=204, content=None)

10. What is the difference between sync and async route handlers?

  • Sync: Blocking; use for CPU-bound or simple I/O. Thread pool handles concurrency.
  • Async: Non-blocking; use for I/O-bound work (DB, HTTP). Better scalability under I/O load.
@app.get("/sync")
def sync_route():
    return {"type": "sync"}

@app.get("/async")
async def async_route():
    return {"type": "async"}

Avoid blocking calls inside async handlers; use async libraries or run in executor.


11. How do you add middleware in FastAPI?

FastAPI uses Starlette middleware. Add it to the app:

from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

@app.middleware("http")
async def add_process_time_header(request, call_next):
    response = await call_next(request)
    response.headers["X-Custom-Header"] = "value"
    return response

12. How do you group routes (e.g. by prefix or tags)?

Use APIRouter and include it in the main app:

from fastapi import APIRouter

router = APIRouter(prefix="/api/v1", tags=["users"])

@router.get("/users")
def list_users():
    return []

app.include_router(router)

13. How does FastAPI generate OpenAPI documentation?

FastAPI builds an OpenAPI schema from route signatures, Pydantic models, and metadata. Docs are served at:

  • Swagger UI: /docs
  • ReDoc: /redoc
  • OpenAPI JSON: /openapi.json

You can customize via title, description, version in FastAPI() and with route summary/description.


14. What are background tasks and when to use them?

Background tasks run after the response is sent. Use for non-critical work that must run once per request (e.g. logging, sending one email), not for heavy or long jobs (use Celery/workers for those).

from fastapi import BackgroundTasks

def send_email(email: str):
    # send email
    pass

@app.post("/register")
def register(user: User, background_tasks: BackgroundTasks):
    background_tasks.add_task(send_email, user.email)
    return {"message": "Registered"}

15. How do you test FastAPI endpoints?

Use TestClient from starlette.testclient (synchronous); it doesn’t run async code in an event loop but is fine for many tests:

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_read_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"Hello": "World"}

For full async testing, use httpx.AsyncClient with ASGITransport.

Interview angle

  • “What’s the request lifecycle?” - ASGI server receives, middleware stack runs outward-in, routing matches, dependencies resolve, the handler runs, the response model filters the output, then middleware unwinds. Knowing where dependencies sit relative to middleware explains most ordering questions.
  • “How do you version an API?” - URL path versioning (/v1, /v2) with separate routers is the pragmatic default: visible, cacheable, easy to route. Header-based versioning is cleaner in theory and harder to debug and cache.
  • “How do you paginate?” - cursor-based for large or changing datasets, because offset pagination skips or repeats rows when data shifts between pages and gets slower the deeper you go.
  • “How do you handle validation errors consistently?” - override the RequestValidationError handler to return your standard error envelope, so clients see one error shape whether the failure came from validation or from business logic.