46 questions - Fresher to experienced

FastAPI interview questions and answers

FastAPI rounds check whether you can build an API that validates input, stays fast under load and is easy to test. Expect Pydantic models and validation, async versus normal endpoints, dependency injection, authentication, database sessions and how you deploy it. In AI-engineer roles it is also how you serve a model or a RAG pipeline.

Last updated:

FastAPI basics

What is FastAPI?

FastAPI is a Python web framework for building APIs. It is built on Starlette for the web layer and Pydantic for data validation, uses Python type hints to validate requests and generate documentation, and runs on an ASGI server such as Uvicorn.

Why is FastAPI fast?

It runs on ASGI, so one process can handle many requests concurrently while they wait on I/O, and Starlette and Pydantic (whose v2 core is written in Rust) keep the per-request overhead low. Most real speed still comes from not blocking the event loop.

What is the difference between ASGI and WSGI?

WSGI is the older synchronous interface used by Flask and Django: one request occupies a worker until it finishes. ASGI is asynchronous and also supports WebSockets and long-lived connections, so a worker can switch to other requests while one waits on a database or network call.

How do you create and run a basic FastAPI app?

Create a FastAPI instance, add path operation functions with decorators such as @app.get, and run it with an ASGI server. In development, uvicorn with --reload restarts on code changes.

from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}

# uvicorn main:app --reload

What is the difference between path parameters and query parameters?

Path parameters are part of the URL path, like /users/42, and identify a resource. Query parameters come after the question mark, like ?limit=10, and usually filter or page results. FastAPI reads both from the function signature and converts them to the declared types.

@app.get("/users/{user_id}")
def get_user(user_id: int, include_orders: bool = False):
    ...

How does FastAPI generate API documentation?

It builds an OpenAPI schema from your routes, type hints and Pydantic models, and serves interactive docs at /docs (Swagger UI) and /redoc (ReDoc). The docs stay in sync with the code because they come from the same declarations.

How do you set the HTTP status code of a response?

Pass status_code to the decorator for the normal case, such as 201 for a created resource or 204 for no content. For other outcomes, raise HTTPException with the right code or return a Response object with an explicit status.

from fastapi import status

@app.post("/orders", status_code=status.HTTP_201_CREATED)
def create_order(order: OrderIn):
    return save(order)

How is FastAPI different from Flask?

Flask is minimal and synchronous by default, and you add validation and docs with extensions. FastAPI has request validation, serialisation, dependency injection and OpenAPI docs built in, and is async-first. Flask suits simple apps; FastAPI suits typed APIs and model serving.

Pydantic models and validation

How do you accept a JSON request body?

Declare a Pydantic model and use it as a parameter type. FastAPI parses the JSON, validates every field against the declared types and constraints, and passes a typed object to your function.

from pydantic import BaseModel, Field

class OrderIn(BaseModel):
    product_id: int
    quantity: int = Field(gt=0, le=100)
    note: str | None = None

@app.post("/orders")
def create_order(order: OrderIn):
    return {"ok": True, "quantity": order.quantity}

What happens when validation fails?

FastAPI rejects the request before your function runs and returns status 422 with a JSON body listing each invalid field, its location and the reason. You can customise this by adding an exception handler for RequestValidationError.

What does response_model do?

It declares the shape of the response. FastAPI validates the returned data against it, filters out fields that are not in the model, such as a password hash, and documents the response in OpenAPI.

class UserOut(BaseModel):
    id: int
    email: str

@app.get("/users/{user_id}", response_model=UserOut)
def read_user(user_id: int):
    return db_get_user(user_id)  # extra fields are dropped

How do you add custom validation in Pydantic v2?

Use @field_validator for one field and @model_validator for rules that involve several fields. Raise ValueError with a clear message; FastAPI turns it into a 422 response.

from pydantic import field_validator

class SignUp(BaseModel):
    username: str

    @field_validator("username")
    @classmethod
    def no_spaces(cls, value: str) -> str:
        if " " in value:
            raise ValueError("username cannot contain spaces")
        return value

What changed between Pydantic v1 and v2 that affects FastAPI code?

Methods were renamed: .dict() became model_dump() and .json() became model_dump_json(); validators became field_validator and model_validator; and Config classes became model_config. Validation is much faster because the core is written in Rust.

How do you add rules to query and path parameters?

Wrap the type in Annotated with Query or Path and give constraints such as min_length, max_length, ge or le. FastAPI validates them like body fields, returns 422 when they fail and shows the rules in the generated docs.

from typing import Annotated
from fastapi import Query

@app.get("/search")
def search(q: Annotated[str, Query(min_length=2, max_length=50)], page: Annotated[int, Query(ge=1)] = 1):
    ...

How do you accept a file upload?

Declare a parameter of type UploadFile, which streams the file to a temporary spool instead of loading it all into memory, and install python-multipart for form parsing. Check the content type and size yourself before saving or processing it.

from fastapi import UploadFile

@app.post("/resume")
async def upload_resume(file: UploadFile):
    data = await file.read()
    return {"name": file.filename, "bytes": len(data)}

How do you use separate models for input and output?

Create one model for what the client sends, such as UserCreate with a password, and another for what you return, such as UserOut without it. This keeps secrets out of responses and lets the two shapes change independently.

Async, concurrency and background work

When should an endpoint be async def and when plain def?

Use async def when the function awaits async libraries, such as an async database driver or httpx.AsyncClient. Use plain def when it calls blocking code; FastAPI runs plain def endpoints in a thread pool so they do not block the event loop.

What happens if you call blocking code inside an async endpoint?

It blocks the event loop, so every other request on that worker waits, and throughput collapses. Either use an async library, make the endpoint a plain def, or move the call to a thread with run_in_threadpool or asyncio.to_thread.

What are background tasks in FastAPI?

BackgroundTasks lets you schedule a function to run after the response is sent, such as sending an email. It runs in the same process, so use a proper queue like Celery or a cloud queue for long, heavy or must-not-fail jobs.

from fastapi import BackgroundTasks

@app.post("/signup")
def signup(user: SignUp, tasks: BackgroundTasks):
    tasks.add_task(send_welcome_email, user.username)
    return {"ok": True}

How do you run code at application startup and shutdown?

Pass a lifespan async context manager to FastAPI. Code before yield runs at startup, such as loading a model or opening a connection pool, and code after yield runs at shutdown. The older on_event hooks are deprecated.

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.model = load_model()
    yield
    app.state.model = None

app = FastAPI(lifespan=lifespan)

How do you stream a response, for example LLM tokens?

Return a StreamingResponse with a generator or async generator that yields chunks. For browser clients, Server-Sent Events with media type text/event-stream is a common format for token streaming.

from fastapi.responses import StreamingResponse

@app.get("/chat")
async def chat(q: str):
    async def tokens():
        async for token in llm_stream(q):
            yield f"data: {token}\n\n"
    return StreamingResponse(tokens(), media_type="text/event-stream")

How do you call another HTTP API from an endpoint without blocking?

Use an async client such as httpx.AsyncClient and await the call. Create one client at startup in the lifespan handler and reuse it, so connections are pooled, and always set a timeout so a slow upstream service cannot hold your workers.

import httpx

@app.get("/weather")
async def weather(city: str):
    response = await app.state.http.get("https://api.example.com/weather", params={"q": city}, timeout=5)
    return response.json()

How do you scale a FastAPI service?

Run several worker processes per machine, for example with Uvicorn workers or Gunicorn managing Uvicorn workers, then run several machines or containers behind a load balancer. Keep the app stateless so any worker can serve any request.

Dependency injection

What is dependency injection in FastAPI?

You declare what an endpoint needs with Depends, such as a database session or the current user, and FastAPI calls those functions and passes the results in. It removes repeated code and makes endpoints easy to test by swapping dependencies.

How do you provide a database session per request?

Write a dependency that yields a session and closes it after the request. Code after yield runs even if the endpoint raises an error, which makes it the right place for cleanup.

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

@app.get("/items")
def list_items(db: Session = Depends(get_db)):
    return db.query(Item).all()

Why do FastAPI docs recommend Annotated with Depends?

Writing db: Annotated[Session, Depends(get_db)] keeps the real type separate from FastAPI metadata, so editors and type checkers see a plain Session. You can also store the Annotated type in a variable, such as DbSession, and reuse it across endpoints instead of repeating Depends.

DbSession = Annotated[Session, Depends(get_db)]

@app.get("/users")
def list_users(db: DbSession):
    return db.query(User).all()

Can dependencies depend on other dependencies?

Yes. A dependency can declare its own Depends parameters, for example get_current_user depends on the token and the database session. FastAPI resolves the whole tree and, by default, calls each dependency once per request even if several parts use it.

How do you replace a dependency in tests?

Set app.dependency_overrides[original] = fake before the test and clear it afterwards. Endpoints then receive the fake, such as an in-memory database or a fixed test user, without changing application code.

How do you apply a dependency to many routes at once?

Pass dependencies=[Depends(...)] to an APIRouter or to the FastAPI app. It runs for every route in that router, which suits checks like authentication or rate limiting whose return value the endpoint does not need.

Security and authentication

How do you implement JWT authentication in FastAPI?

Use OAuth2PasswordBearer to read the bearer token from the Authorization header, verify the token signature and expiry in a get_current_user dependency, and raise HTTPException 401 if it fails. Protected endpoints then depend on get_current_user.

oauth2 = OAuth2PasswordBearer(tokenUrl="token")

def get_current_user(token: str = Depends(oauth2)):
    payload = decode_jwt(token)  # verify signature and expiry
    if payload is None:
        raise HTTPException(status_code=401, detail="Invalid token")
    return payload["sub"]

How should passwords be stored?

Never store them as plain text or with a fast hash like SHA-256. Store a slow, salted hash made with bcrypt or Argon2, and compare with the library's verify function when the user logs in.

How do you restrict an endpoint to certain roles?

Write a dependency factory that takes the allowed roles and returns a dependency. It reads the current user from get_current_user and raises 403 if the user's role is not allowed. Each endpoint then declares the roles it needs in one line.

def require_role(*roles):
    def checker(user = Depends(get_current_user)):
        if user.role not in roles:
            raise HTTPException(status_code=403, detail="Not allowed")
        return user
    return checker

@app.delete("/users/{user_id}", dependencies=[Depends(require_role("admin"))])
def delete_user(user_id: int): ...

How do you enable CORS?

Add CORSMiddleware with the exact frontend origins you trust, the methods and headers you allow, and allow_credentials only if you need cookies. Avoid allow_origins=["*"] together with credentials.

How do you return a proper error from an endpoint?

Raise HTTPException with a status code and a detail message, for example 404 when a record does not exist. For your own exception types, register an exception handler that converts them to a consistent JSON error format.

How would you protect an API with an API key?

Read the key from a header with APIKeyHeader in a dependency, compare it with stored keys using a constant-time comparison, and raise 401 or 403 on failure. Store only hashed keys and allow rotating them.

Databases and project structure

How do you use SQLAlchemy with FastAPI?

Create an engine and a session factory, provide a session per request through a dependency, define ORM models for tables and Pydantic models for the API, and convert between them. With SQLAlchemy 2.0 you can use the async engine and await queries.

How do you organise a larger FastAPI project?

Split routes into APIRouter modules by feature, such as users and orders, include them in the main app with prefixes and tags, and keep schemas, database models, services and settings in separate modules so endpoints stay thin.

router = APIRouter(prefix="/orders", tags=["orders"])

@router.get("/")
def list_orders(): ...

app.include_router(router)

How do you manage configuration and secrets?

Use pydantic-settings: a BaseSettings class reads typed values from environment variables or a .env file and fails at startup if a required value is missing. Never hard-code secrets in the code.

How do you handle database schema changes?

Use a migration tool such as Alembic, which records each schema change as a versioned script. Generate migrations from model changes, review them, and run them as a separate deployment step before the new code starts.

How would you serve a machine learning model with FastAPI?

Load the model once at startup in the lifespan handler, validate inputs with a Pydantic model, run inference in a thread pool or a separate worker if it is CPU-heavy, and return a typed response. Add a health endpoint and batch requests if the model supports it.

Testing and deployment

How do you test FastAPI endpoints?

Use TestClient, which calls the app in-process without a running server, with pytest. Override dependencies for the database or authentication, call endpoints, and assert on status codes and JSON bodies.

from fastapi.testclient import TestClient

client = TestClient(app)

def test_health():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}

How do you test async code and a real database in FastAPI?

For async tests, use httpx.AsyncClient with ASGITransport(app=app) and an async test plugin such as AnyIO's pytest plugin or pytest-asyncio. For the database, point the app at a test database, wrap each test in a transaction that rolls back, and override get_db to use that session.

What is middleware in FastAPI and when would you write one?

Middleware wraps every request and response. Use it for cross-cutting concerns such as request IDs, timing and logging, or security headers. Keep it light, because it runs on every call.

How do you deploy FastAPI in production?

Package it in a Docker image, run it with Uvicorn workers or Gunicorn managing Uvicorn workers, put it behind a reverse proxy or load balancer that handles TLS, and configure health checks, logging and environment-based settings.

How do you add rate limiting?

Either enforce limits at the API gateway or reverse proxy, or in the app with a library such as slowapi or a Redis-backed counter keyed by user or IP. Gateway limits protect the app from load; app limits can depend on the user's plan.

Does FastAPI support WebSockets?

Yes. Declare a route with @app.websocket, accept the connection, then receive and send messages in a loop until the client disconnects. It suits live chat, notifications and streaming results.

Continue with another topic

Practise live

CrackInterviewAI listens to the live interview and gives a structured answer on screen for coding, system design, HR and project questions. Download for Windows.