[{"data":1,"prerenderedAt":217},["ShallowReactive",2],{"topic-fastapi-deployment":3},{"framework":4,"topic":15,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Ffastapi.yml","FastAPI interview questions on async routing, Pydantic validation, dependency injection, OAuth2 security, database integration and deployment — the go-to Python framework for production APIs.","yml","fastapi",{},"FastAPI",6,"frameworks\u002Ffastapi",1,"lgr_X74wBdBYovbrlazGWPWqqi-YwNEUq44l1BmtgyE",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":20,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Ffastapi-deployment.yml","Uvicorn, Gunicorn, CORS middleware and BackgroundTasks — deploying FastAPI apps and tuning them for production traffic.",{},"Deployment & Middleware",8,"deployment","topics\u002Ffastapi-deployment","Dok_4ZK-VioVVTjvSNpbt4_sKd1DLevjkz9kGwkpQUQ",[25,95,158],{"id":26,"title":27,"body":28,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":37,"navigation":38,"order":13,"path":39,"questions":40,"questionsCount":87,"related":88,"seo":89,"seoDescription":90,"stem":91,"subtopic":92,"topic":19,"topicSlug":21,"updated":93,"__hash__":94},"qa\u002Ffastapi\u002Fdeployment\u002Fuvicorn-gunicorn.md","Uvicorn Gunicorn",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":33,"depth":33,"links":34},"",2,[],"medium","md",{},true,"\u002Ffastapi\u002Fdeployment\u002Fuvicorn-gunicorn",[41,46,50,54,58,62,66,70,75,79,83],{"id":42,"difficulty":43,"q":44,"a":45},"uvicorn-basics","easy","What is Uvicorn and why is it the recommended server for FastAPI?","**Uvicorn** is a lightning-fast ASGI server built on `uvloop` (a C-accelerated\nasyncio event loop) and `httptools`. FastAPI requires an ASGI server because\nit's built on Starlette, which uses the ASGI protocol.\n\n```bash\npip install uvicorn[standard]   # includes uvloop and httptools\nuvicorn app.main:app --host 0.0.0.0 --port 8000\n```\n\nKey flags:\n- `--reload` — hot reload for development\n- `--workers N` — multiple processes (single worker = one event loop)\n- `--host 0.0.0.0` — listen on all interfaces\n- `--port 8000`\n\nRule of thumb: use `uvicorn[standard]` in production for the `uvloop` speedup;\nuse plain `uvicorn` in Docker to keep the image small (uvloop has C deps).\n",{"id":47,"difficulty":35,"q":48,"a":49},"gunicorn-uvicorn-workers","Why combine Gunicorn with Uvicorn workers and how do you configure it?","Uvicorn alone handles one event loop per process. **Gunicorn** is a battle-tested\nprocess manager that handles worker lifecycle (respawning crashed workers,\ngraceful restarts, signal handling). Together they give you:\n- Gunicorn's robust process management.\n- Uvicorn's async event loop per worker.\n\n```bash\npip install gunicorn\ngunicorn app.main:app \\\n    -k uvicorn.workers.UvicornWorker \\\n    --workers 4 \\\n    --bind 0.0.0.0:8000 \\\n    --timeout 120\n```\n\n`UvicornWorker` replaces Gunicorn's default sync worker with an async one.\n\nRule of thumb: use Gunicorn + `UvicornWorker` for traditional deployments on\nVMs\u002Fbare metal; use Uvicorn directly in Kubernetes where the orchestrator handles\npod restarts.\n",{"id":51,"difficulty":35,"q":52,"a":53},"worker-count","How many workers should you run per server for a FastAPI app?","The classic formula: **`workers = 2 × CPU_cores + 1`**.\n\n```bash\n# 4-core machine → 9 workers\ngunicorn app.main:app -k uvicorn.workers.UvicornWorker --workers 9\n```\n\nHowever, FastAPI is async — a single worker handles many concurrent requests\nthrough the event loop. For I\u002FO-bound apps (most web APIs), 2-4 workers per\nmachine is often sufficient:\n\n| App type | Worker count |\n|----------|-------------|\n| Pure async I\u002FO | 2–4 per machine |\n| Mixed sync\u002Fasync | 2 × cores |\n| CPU-bound | 1 per core (use multiprocessing separately) |\n\nRule of thumb: start with `2 × cores + 1`; profile under load and reduce if\nworkers share limited resources (DB connections, RAM).\n",{"id":55,"difficulty":35,"q":56,"a":57},"concurrency-vs-parallelism","What is the difference between concurrency and parallelism in the context of FastAPI?","- **Concurrency**: multiple tasks make progress by interleaving on a single CPU\n  (one event loop thread handles thousands of waiting I\u002FO operations).\n- **Parallelism**: multiple tasks run simultaneously on multiple CPUs (multiple\n  Uvicorn worker processes, each with their own event loop).\n\nFastAPI gives you **concurrency** within a single worker via `async def` handlers.\nYou get **parallelism** by running multiple workers.\n\n```\nWorker 1 (CPU core 1): event loop handles 1000 concurrent requests\nWorker 2 (CPU core 2): event loop handles 1000 concurrent requests\n```\n\nCPU-bound code (heavy computation) blocks a core — neither concurrency nor more\n`async def` helps. Use a thread\u002Fprocess pool or a task queue.\n\nRule of thumb: `async def` handlers add concurrency (better I\u002FO throughput per\nworker); more workers add parallelism (better CPU utilisation).\n",{"id":59,"difficulty":43,"q":60,"a":61},"reload-dev","How do you enable hot reload in Uvicorn for development?","Pass `--reload` flag:\n\n```bash\nuvicorn app.main:app --reload --reload-dir app\n```\n\nOr use the Python API (better for IDEs):\n```python\n# run.py\nimport uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.main:app\", host=\"0.0.0.0\", port=8000, reload=True)\n```\n\n`--reload-dir app` restricts watching to the `app\u002F` directory, avoiding\nfalse reloads when `.pyc` files or test outputs change.\n\nRule of thumb: never use `--reload` in production — it adds overhead and\nrestarts the process on any file change, including logs and temp files.\n",{"id":63,"difficulty":35,"q":64,"a":65},"docker-fastapi","What is the recommended Dockerfile structure for a FastAPI application?","```dockerfile\nFROM python:3.12-slim\n\nWORKDIR \u002Fapp\n\n# Install dependencies first (layer cached until requirements change)\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\n# Copy application code\nCOPY .\u002Fapp .\u002Fapp\n\nEXPOSE 8000\n\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--workers\", \"4\"]\n```\n\nMulti-stage build for smaller images:\n```dockerfile\nFROM python:3.12-slim AS builder\nRUN pip install --no-cache-dir -r requirements.txt --target \u002Finstall\n\nFROM python:3.12-slim\nCOPY --from=builder \u002Finstall \u002Fusr\u002Flocal\u002Flib\u002Fpython3.12\u002Fsite-packages\nCOPY .\u002Fapp .\u002Fapp\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n```\n\nRule of thumb: use `--no-cache-dir` in pip installs to keep image size down;\ncopy `requirements.txt` before source code for layer caching.\n",{"id":67,"difficulty":43,"q":68,"a":69},"health-check","How do you add a health check endpoint in FastAPI for container orchestration?","```python\nfrom fastapi import FastAPI\nfrom fastapi.responses import JSONResponse\n\napp = FastAPI()\n\n@app.get(\"\u002Fhealth\", include_in_schema=False)\nasync def health():\n    return {\"status\": \"ok\"}\n\n# Liveness (is the process alive?)\n@app.get(\"\u002Fhealth\u002Flive\", include_in_schema=False)\nasync def liveness():\n    return {\"status\": \"alive\"}\n\n# Readiness (is the app ready to serve traffic?)\n@app.get(\"\u002Fhealth\u002Fready\", include_in_schema=False)\nasync def readiness():\n    try:\n        await db.execute(\"SELECT 1\")\n    except Exception:\n        return JSONResponse({\"status\": \"not ready\"}, status_code=503)\n    return {\"status\": \"ready\"}\n```\n\nKubernetes `livenessProbe` uses `\u002Fhealth\u002Flive`; `readinessProbe` uses `\u002Fhealth\u002Fready`.\n\nRule of thumb: readiness should check actual dependencies (DB, cache);\nliveness should only check the process is alive — failing liveness kills the pod.\n",{"id":71,"difficulty":72,"q":73,"a":74},"graceful-shutdown","hard","How does FastAPI\u002FUvicorn handle graceful shutdown?","When Uvicorn receives **SIGTERM** (sent by Kubernetes, Docker, or Gunicorn):\n1. It stops accepting new connections.\n2. Waits for in-flight requests to complete (up to `--timeout-graceful-shutdown` seconds).\n3. Calls the `lifespan` shutdown code (after `yield`).\n4. Closes the event loop and exits.\n\n```bash\nuvicorn app.main:app --timeout-graceful-shutdown 30\n```\n\nIn Kubernetes, set a `preStop` hook to delay pod termination so the load\nbalancer routes traffic away before the pod stops accepting connections:\n\n```yaml\nlifecycle:\n    preStop:\n        exec:\n            command: [\"sleep\", \"5\"]\n```\n\nRule of thumb: always set `--timeout-graceful-shutdown` to slightly less than\nKubernetes' `terminationGracePeriodSeconds` to give in-flight requests time to finish.\n",{"id":76,"difficulty":43,"q":77,"a":78},"environment-variables-uvicorn","How do you pass environment variables to a FastAPI app running in Docker?","Pass them at container run time (don't bake secrets into the image):\n\n```bash\ndocker run -e DATABASE_URL=postgresql:\u002F\u002F... -e SECRET_KEY=... myapp\n```\n\nOr use a `.env` file:\n```bash\ndocker run --env-file .env.production myapp\n```\n\nIn Docker Compose:\n```yaml\nservices:\n    api:\n        image: myapp\n        environment:\n            - DATABASE_URL=postgresql:\u002F\u002Fdb\u002Fmydb\n            - SECRET_KEY=${SECRET_KEY}\n```\n\nIn Kubernetes, use `Secrets` (base64-encoded) for sensitive values:\n```yaml\nenvFrom:\n    - secretRef:\n        name: myapp-secrets\n```\n\nRule of thumb: never embed production secrets in the Docker image or\n`docker-compose.yml` — always inject at runtime from secrets management.\n",{"id":80,"difficulty":72,"q":81,"a":82},"multiple-apps-mount","How do you serve multiple FastAPI apps from a single Uvicorn process?","Mount sub-applications using Starlette's `Mount`:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import Mount\nfrom fastapi import FastAPI\n\nv1_app = FastAPI(title=\"API v1\")\nv2_app = FastAPI(title=\"API v2\")\n\n@v1_app.get(\"\u002Fitems\")\nasync def v1_items(): return [{\"version\": \"v1\"}]\n\n@v2_app.get(\"\u002Fitems\")\nasync def v2_items(): return [{\"version\": \"v2\", \"new_field\": True}]\n\n# Root app that routes between them\nroot_app = Starlette(routes=[\n    Mount(\"\u002Fv1\", app=v1_app),\n    Mount(\"\u002Fv2\", app=v2_app),\n])\n```\n\n```bash\nuvicorn main:root_app --port 8000\n# \u002Fv1\u002Fitems → v1_app; \u002Fv2\u002Fitems → v2_app\n# \u002Fv1\u002Fdocs and \u002Fv2\u002Fdocs each work independently\n```\n\nRule of thumb: mount versioned sub-apps when versions differ so significantly\nthat sharing middleware or the OpenAPI schema would be confusing.\n",{"id":84,"difficulty":35,"q":85,"a":86},"ssl-tls-uvicorn","How do you run Uvicorn with SSL\u002FTLS in development?","Pass `--ssl-keyfile` and `--ssl-certfile`:\n\n```bash\n# Generate self-signed cert for dev\nopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes\n\nuvicorn app.main:app --ssl-keyfile key.pem --ssl-certfile cert.pem --port 8443\n```\n\nIn production, **don't terminate TLS in Uvicorn** — use Nginx\u002FCaddy\u002FALB in front.\nTLS termination in the reverse proxy lets you use `certbot` for Let's Encrypt,\nhandle certificate rotation without restarting Uvicorn, and offload TLS overhead.\n\nRule of thumb: Uvicorn TLS is fine for internal service-to-service encryption\nor local dev HTTPS; for public-facing production use a reverse proxy for TLS.\n",11,null,{"description":32},"FastAPI Uvicorn and Gunicorn interview questions — workers, concurrency model, process management, Docker deployment and performance tuning.","fastapi\u002Fdeployment\u002Fuvicorn-gunicorn","Uvicorn & Gunicorn","2026-06-20","NB_fUy4qlVwanwlzoDBtcJM_FUAdfFUHj-XXR8yQdyc",{"id":96,"title":97,"body":98,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":102,"navigation":38,"order":33,"path":103,"questions":104,"questionsCount":153,"related":88,"seo":154,"seoDescription":155,"stem":156,"subtopic":97,"topic":19,"topicSlug":21,"updated":93,"__hash__":157},"qa\u002Ffastapi\u002Fdeployment\u002Fmiddleware.md","Middleware",{"type":29,"value":99,"toc":100},[],{"title":32,"searchDepth":33,"depth":33,"links":101},[],{},"\u002Ffastapi\u002Fdeployment\u002Fmiddleware",[105,109,113,117,121,125,129,133,137,141,145,149],{"id":106,"difficulty":43,"q":107,"a":108},"middleware-basics","What is middleware in FastAPI and how does it wrap requests?","Middleware is code that runs **around every request** — before the handler\n(on the way in) and after the handler (on the way out). It can modify the\nrequest, modify the response, short-circuit with an early response, or just\nobserve (logging, timing).\n\n```python\nfrom starlette.middleware.base import BaseHTTPMiddleware\nfrom fastapi import Request\n\nclass TimingMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request: Request, call_next):\n        import time\n        start = time.perf_counter()\n        response = await call_next(request)   # call the rest of the stack\n        elapsed = time.perf_counter() - start\n        response.headers[\"X-Process-Time\"] = f\"{elapsed:.4f}\"\n        return response\n\napp.add_middleware(TimingMiddleware)\n```\n\nRule of thumb: middleware is for cross-cutting concerns (timing, CORS, auth\nheaders, compression); use dependencies for per-route business logic.\n",{"id":110,"difficulty":35,"q":111,"a":112},"middleware-order","What order does middleware execute in FastAPI?","Middleware added with `add_middleware()` wraps the app in **reverse addition\norder** — last added = outermost wrapper = runs first for requests.\n\n```python\napp.add_middleware(A)   # added first → innermost\napp.add_middleware(B)   # added second → middle\napp.add_middleware(C)   # added last  → outermost\n\n# Request:  C → B → A → handler\n# Response: handler → A → B → C\n```\n\nBuilt-in middleware (CORS, GZip, TrustedHost) should typically be outermost\n(added last) so they wrap everything including custom middleware.\n\nRule of thumb: add CORS middleware last (so it wraps everything) and logging\nmiddleware first (so its timing includes all other middleware).\n",{"id":114,"difficulty":35,"q":115,"a":116},"cors-middleware","How do you configure CORS middleware in FastAPI and what are the critical settings?","```python\nfrom fastapi.middleware.cors import CORSMiddleware\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"https:\u002F\u002Fapp.example.com\", \"https:\u002F\u002Fadmin.example.com\"],\n    allow_credentials=True,     # required for cookies\u002Fauth headers cross-origin\n    allow_methods=[\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"],\n    allow_headers=[\"Content-Type\", \"Authorization\", \"X-Request-ID\"],\n    expose_headers=[\"X-Total-Count\"],   # headers the browser can read\n    max_age=3600,               # preflight cache duration (seconds)\n)\n```\n\nCritical interaction: `allow_origins=[\"*\"]` + `allow_credentials=True` is\n**invalid** — browsers reject this combination. You must list exact origins\nwhen using credentials.\n\nRule of thumb: list exact origins, never `\"*\"`, for APIs that send or receive\nauth cookies or `Authorization` headers.\n",{"id":118,"difficulty":43,"q":119,"a":120},"gzip-middleware","How do you enable GZip compression for FastAPI responses?","```python\nfrom starlette.middleware.gzip import GZipMiddleware\n\napp.add_middleware(GZipMiddleware, minimum_size=1000)\n```\n\n`minimum_size` (bytes) — responses smaller than this threshold are not\ncompressed (compression overhead isn't worth it for tiny responses).\n\nGZipMiddleware respects `Accept-Encoding: gzip` from the client — clients\nthat don't support gzip get uncompressed responses.\n\nRule of thumb: enable GZip for APIs that return large JSON payloads (lists of\nitems, reports); don't bother for APIs that only return small objects.\n",{"id":122,"difficulty":43,"q":123,"a":124},"trusted-host-middleware","What does `TrustedHostMiddleware` do and why is it important?","It validates the `Host` header against an allowlist, returning **400** if the\nhost doesn't match. This prevents **Host header injection attacks**:\n\n```python\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\n\napp.add_middleware(\n    TrustedHostMiddleware,\n    allowed_hosts=[\"api.example.com\", \"*.example.com\"],\n)\n```\n\nWithout it, an attacker who routes a request to your server with a forged `Host`\nheader can exploit password reset links, CORS checks, or server-generated URLs\nthat rely on `request.base_url`.\n\nRule of thumb: add `TrustedHostMiddleware` before deploying publicly; list\nall valid hostnames including your CDN\u002Fload balancer's hostname.\n",{"id":126,"difficulty":43,"q":127,"a":128},"https-redirect","How do you redirect all HTTP traffic to HTTPS in FastAPI?","```python\nfrom starlette.middleware.httpsredirect import HTTPSRedirectMiddleware\n\napp.add_middleware(HTTPSRedirectMiddleware)\n```\n\nAll HTTP requests receive a **307 Temporary Redirect** to the HTTPS equivalent.\nBehind a reverse proxy that terminates TLS, the proxy should handle the redirect\ninstead — the FastAPI middleware is a fallback.\n\nNote: this middleware checks the `X-Forwarded-Proto` header set by reverse proxies.\nConfigure Nginx\u002FALB to set this header; otherwise the middleware may redirect\nHTTPS requests (which arrive to Uvicorn as plain HTTP).\n\nRule of thumb: use `HTTPSRedirectMiddleware` as defence-in-depth; configure the\nprimary HTTP→HTTPS redirect at the reverse proxy level.\n",{"id":130,"difficulty":35,"q":131,"a":132},"base-http-middleware","What is `BaseHTTPMiddleware` and what is its performance trade-off?","`BaseHTTPMiddleware` provides a high-level `async dispatch(request, call_next)` \ninterface. It's easy to write but has a performance cost:\n- It buffers the response into memory before passing it to the next middleware.\n- It cannot stream responses incrementally.\n- It adds overhead compared to pure ASGI middleware.\n\n```python\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        request.state.request_id = str(uuid4())\n        response = await call_next(request)\n        response.headers[\"X-Request-ID\"] = request.state.request_id\n        return response\n```\n\nFor high-performance production middleware, implement the raw ASGI interface:\n```python\nclass RawMiddleware:\n    def __init__(self, app):\n        self.app = app\n    async def __call__(self, scope, receive, send):\n        ...\n        await self.app(scope, receive, send)\n```\n\nRule of thumb: use `BaseHTTPMiddleware` for simplicity unless profiling shows\nit as a bottleneck; use raw ASGI middleware for streaming responses.\n",{"id":134,"difficulty":72,"q":135,"a":136},"middleware-exception-handling","Can middleware catch exceptions raised by route handlers?","Yes — exceptions propagate through the middleware stack. Middleware can catch\nthem with `try\u002Fexcept` around `call_next()`:\n\n```python\nclass ErrorLoggingMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        try:\n            response = await call_next(request)\n        except Exception as e:\n            logger.error(\"Unhandled error: %s\", e, exc_info=True)\n            return JSONResponse({\"detail\": \"Internal server error\"}, status_code=500)\n        return response\n```\n\nHowever, `HTTPException` is caught by FastAPI's exception handler **before**\npropagating through middleware — middleware doesn't see handled `HTTPException`\nresponses.\n\nRule of thumb: use middleware to catch **unexpected** exceptions (for logging);\nuse `@app.exception_handler` to convert **expected** domain exceptions to HTTP responses.\n",{"id":138,"difficulty":35,"q":139,"a":140},"request-id-middleware","How do you implement a request ID middleware for distributed tracing?","```python\nimport uuid\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nclass RequestIDMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        # Use X-Request-ID if provided (from upstream), else generate one\n        req_id = request.headers.get(\"X-Request-ID\", str(uuid.uuid4()))\n        request.state.request_id = req_id\n        response = await call_next(request)\n        response.headers[\"X-Request-ID\"] = req_id\n        return response\n\napp.add_middleware(RequestIDMiddleware)\n```\n\nIn handlers and dependencies, access via `request.state.request_id` and include\nit in log records for correlation.\n\nRule of thumb: always propagate the request ID from upstream if present — it\nallows tracing a request across multiple services in a distributed system.\n",{"id":142,"difficulty":35,"q":143,"a":144},"session-middleware","How do you add server-side sessions to FastAPI?","Use `starlette.middleware.sessions.SessionMiddleware` (cookie-signed sessions):\n\n```python\nfrom starlette.middleware.sessions import SessionMiddleware\n\napp.add_middleware(\n    SessionMiddleware,\n    secret_key=settings.secret_key,\n    session_cookie=\"session\",\n    max_age=3600,     # 1 hour\n    https_only=True,  # cookie sent over HTTPS only\n    same_site=\"lax\",\n)\n```\n\nAccess in handlers:\n```python\n@app.get(\"\u002Fme\")\nasync def me(request: Request):\n    user_id = request.session.get(\"user_id\")\n    if not user_id:\n        raise HTTPException(401)\n    return {\"user_id\": user_id}\n\n@app.post(\"\u002Flogin\")\nasync def login(request: Request, ...):\n    request.session[\"user_id\"] = user.id\n    return {\"status\": \"logged in\"}\n```\n\nRule of thumb: Starlette sessions store data in a signed cookie — all session\ndata is sent to the browser. Use Redis-backed sessions for sensitive data.\n",{"id":146,"difficulty":72,"q":147,"a":148},"rate-limit-middleware","How do you implement rate limiting as middleware in FastAPI?","```python\nimport time\nfrom collections import defaultdict\nfrom fastapi import Request\nfrom fastapi.responses import JSONResponse\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nclass RateLimitMiddleware(BaseHTTPMiddleware):\n    def __init__(self, app, max_calls: int = 100, period: int = 60):\n        super().__init__(app)\n        self.max_calls = max_calls\n        self.period = period\n        self.calls: dict[str, list[float]] = defaultdict(list)\n\n    async def dispatch(self, request: Request, call_next):\n        key = request.client.host\n        now = time.time()\n        window_start = now - self.period\n\n        # Keep only calls within the window\n        self.calls[key] = [t for t in self.calls[key] if t > window_start]\n\n        if len(self.calls[key]) >= self.max_calls:\n            return JSONResponse({\"detail\": \"Rate limit exceeded\"}, status_code=429)\n\n        self.calls[key].append(now)\n        return await call_next(request)\n\napp.add_middleware(RateLimitMiddleware, max_calls=100, period=60)\n```\n\nFor production use Redis instead of in-memory dict (shared across workers).\n\nRule of thumb: the in-memory dict is per-worker — each worker has separate counters.\nUse Redis for a global rate limit that works across all workers.\n",{"id":150,"difficulty":35,"q":151,"a":152},"middleware-vs-dependency-auth","Should authentication be done in middleware or as a dependency?","| Approach | Pros | Cons |\n|----------|------|------|\n| Middleware | Runs before routing; can block early | Can't access route params; not testable via `dependency_overrides` |\n| Dependency | Testable; has route context; composable | Only runs after routing |\n\nBest practice — **hybrid**:\n- Middleware: lightweight token presence check (is `Authorization` header present?).\n- Dependency: full JWT validation + DB user lookup.\n\n```python\n# middleware — fast early reject\nclass BearerPresenceMiddleware(BaseHTTPMiddleware):\n    UNPROTECTED = {\"\u002Ftoken\", \"\u002Fdocs\", \"\u002Fhealth\"}\n    async def dispatch(self, request, call_next):\n        if request.url.path not in self.UNPROTECTED:\n            if not request.headers.get(\"Authorization\"):\n                return JSONResponse({\"detail\": \"Missing token\"}, status_code=401)\n        return await call_next(request)\n\n# dependency — full validation\nasync def get_current_user(token = Depends(oauth2_scheme)):\n    ...\n```\n\nRule of thumb: use middleware for coarse early rejection; use dependencies for\nfine-grained auth with route context.\n",12,{"description":32},"FastAPI middleware interview questions — BaseHTTPMiddleware, CORS, GZip, trusted hosts, custom middleware, execution order and performance.","fastapi\u002Fdeployment\u002Fmiddleware","cUfNGerUgiltPnnDvZoyS0WmvV0LGhA6NzX308xPsgY",{"id":159,"title":160,"body":161,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":165,"navigation":38,"order":166,"path":167,"questions":168,"questionsCount":87,"related":88,"seo":213,"seoDescription":214,"stem":215,"subtopic":160,"topic":19,"topicSlug":21,"updated":93,"__hash__":216},"qa\u002Ffastapi\u002Fdeployment\u002Fbackground-tasks.md","Background Tasks",{"type":29,"value":162,"toc":163},[],{"title":32,"searchDepth":33,"depth":33,"links":164},[],{},3,"\u002Ffastapi\u002Fdeployment\u002Fbackground-tasks",[169,173,177,181,185,189,193,197,201,205,209],{"id":170,"difficulty":43,"q":171,"a":172},"background-tasks-basics","What are `BackgroundTasks` in FastAPI and how do you use them?","`BackgroundTasks` lets you schedule functions to run **after the HTTP response\nis sent** to the client. The handler returns immediately; the task runs in the\nsame process after the response is fully written.\n\n```python\nfrom fastapi import BackgroundTasks, FastAPI\n\napp = FastAPI()\n\ndef write_log(message: str):\n    with open(\"log.txt\", \"a\") as f:\n        f.write(message + \"\\n\")\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    result = await db.save(item)\n    background_tasks.add_task(write_log, f\"Created item {result.id}\")\n    return result   # response sent; write_log runs after\n```\n\nRule of thumb: inject `BackgroundTasks` as a parameter — FastAPI provides it\nautomatically; never instantiate it yourself.\n",{"id":174,"difficulty":35,"q":175,"a":176},"async-background-task","Can background tasks in FastAPI be async functions?","Yes — `add_task` accepts both `async def` and regular `def` functions.\n\n```python\nasync def send_welcome_email(email: str):\n    await smtp_client.send(email, \"Welcome!\")\n\ndef sync_audit_log(event: str):\n    logger.info(event)    # sync — runs in thread pool\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate, background_tasks: BackgroundTasks):\n    new_user = await db.create(user)\n    background_tasks.add_task(send_welcome_email, new_user.email)   # async\n    background_tasks.add_task(sync_audit_log, f\"signup:{new_user.id}\")  # sync\n    return new_user\n```\n\nAsync tasks run on the event loop; sync tasks run in the thread pool.\nTasks execute **sequentially** in the order they were added.\n\nRule of thumb: prefer async background tasks in an async app — they don't\nblock the event loop and don't require thread pool slots.\n",{"id":178,"difficulty":35,"q":179,"a":180},"background-task-ordering","In what order do multiple `BackgroundTasks` run?","**Sequentially, in the order they were added.** If task A runs 2 seconds and\ntask B runs 1 second, total background time is 3 seconds.\n\n```python\nbackground_tasks.add_task(task_a)   # runs first\nbackground_tasks.add_task(task_b)   # runs second, after task_a completes\n```\n\nFor parallel background work, use `asyncio.gather()` inside a single async task:\n\n```python\nasync def parallel_tasks(email: str, item_id: int):\n    await asyncio.gather(\n        send_email(email),\n        update_analytics(item_id),\n    )\n\nbackground_tasks.add_task(parallel_tasks, user.email, item.id)\n```\n\nRule of thumb: if background tasks are independent and their combined duration\nmatters, wrap them in an `asyncio.gather` inside a single background task.\n",{"id":182,"difficulty":35,"q":183,"a":184},"background-task-error-handling","What happens if a background task raises an exception in FastAPI?","The exception is logged (Uvicorn logs it as an unhandled exception) but\n**does not affect the response** — it was already sent. The client never\nknows the task failed.\n\n```python\ndef risky_task():\n    raise ValueError(\"Something went wrong\")   # logged, response already sent\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    background_tasks.add_task(risky_task)\n    return item   # this 200 response is already sent before risky_task runs\n```\n\nFor reliable task execution (retries, persistence), use a proper task queue\n(Celery, ARQ, RQ) where tasks can be retried on failure.\n\nRule of thumb: use `BackgroundTasks` only for best-effort work (email\nnotification, audit logging); never for work that must succeed (payment processing,\ndata integrity).\n",{"id":186,"difficulty":35,"q":187,"a":188},"background-task-vs-queue","When should you use `BackgroundTasks` vs a proper task queue like Celery?","| Concern | BackgroundTasks | Task queue (Celery\u002FARQ) |\n|---------|----------------|------------------------|\n| Must succeed | ❌ no retries | ✅ configurable retries |\n| Survives crash\u002Frestart | ❌ lost if process dies | ✅ persisted in broker |\n| Long-running (minutes) | ❌ blocks worker | ✅ separate worker |\n| Rate-limited external API | ❌ no throttling | ✅ rate limiting |\n| Monitor\u002Finspect tasks | ❌ | ✅ Flower, ARQ dashboard |\n| Simple fire-and-forget | ✅ | overkill |\n\n```python\n# BackgroundTasks — fine for quick, best-effort\nbackground_tasks.add_task(send_analytics_event, event_data)\n\n# Celery — required for reliability\nprocess_payment.delay(order_id)   # persisted in Redis\u002FRabbitMQ\n```\n\nRule of thumb: if the task must succeed, use a task queue; if losing it\noccasionally is acceptable, `BackgroundTasks` is simpler.\n",{"id":190,"difficulty":72,"q":191,"a":192},"celery-fastapi","How do you integrate Celery with a FastAPI application?","```python\n# celery_app.py\nfrom celery import Celery\n\ncelery = Celery(\n    \"myapp\",\n    broker=\"redis:\u002F\u002Flocalhost:6379\u002F0\",\n    backend=\"redis:\u002F\u002Flocalhost:6379\u002F1\",\n)\n\n@celery.task\ndef send_email_task(to: str, subject: str, body: str):\n    smtp.send(to, subject, body)\n```\n\n```python\n# FastAPI route\nfrom celery_app import send_email_task\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate):\n    new_user = await db.create(user)\n    send_email_task.delay(new_user.email, \"Welcome!\", \"...\")\n    return new_user\n```\n\nStart workers separately:\n```bash\ncelery -A celery_app worker --loglevel=info\n```\n\nRule of thumb: keep Celery tasks in a separate module from FastAPI routes —\ntasks should be importable by Celery workers that don't start the web server.\n",{"id":194,"difficulty":72,"q":195,"a":196},"arq-fastapi","What is ARQ and why might you prefer it over Celery for a FastAPI app?","**ARQ** (Async Redis Queue) is a task queue built for asyncio — tasks are `async def`\nfunctions, the worker is an event loop, and it uses Redis as the broker.\n\n```python\n# tasks.py\nfrom arq import cron\n\nasync def send_welcome_email(ctx, email: str):\n    await ctx[\"smtp\"].send(email, \"Welcome!\")\n\nclass WorkerSettings:\n    functions = [send_welcome_email]\n    redis_settings = RedisSettings()\n```\n\n```python\n# FastAPI\nfrom arq import create_pool\n\n@asynccontextmanager\nasync def lifespan(app):\n    app.state.arq = await create_pool(RedisSettings())\n    yield\n    await app.state.arq.aclose()\n\n@app.post(\"\u002Fsignup\")\nasync def signup(user: UserCreate, request: Request):\n    await request.app.state.arq.enqueue_job(\"send_welcome_email\", user.email)\n    return {\"status\": \"created\"}\n```\n\nARQ advantages over Celery for async apps: native async, simpler setup,\nno serialisation overhead for Python objects, type hints work naturally.\n\nRule of thumb: choose ARQ for async-first apps; choose Celery when you need\nmature monitoring (Flower), scheduled tasks (Celery Beat), or a non-Python worker ecosystem.\n",{"id":198,"difficulty":72,"q":199,"a":200},"background-task-db-session","How do you use a database session inside a FastAPI background task?","Background tasks run **after the request session is closed** — you cannot reuse\nthe request's DB session. Create a new session inside the task:\n\n```python\nfrom sqlalchemy.orm import Session\nfrom app.db import SessionLocal\n\ndef send_order_confirmation(order_id: int):\n    db = SessionLocal()\n    try:\n        order = db.get(Order, order_id)\n        send_email(order.user.email, f\"Order {order_id} confirmed\")\n        db.commit()\n    finally:\n        db.close()\n\n@app.post(\"\u002Forders\")\nasync def create_order(data: OrderCreate, background_tasks: BackgroundTasks):\n    order = await db.create(data)\n    background_tasks.add_task(send_order_confirmation, order.id)\n    return order\n```\n\nPass the **ID**, not the ORM object — ORM objects are tied to a session; they\ncan't be used in a different session.\n\nRule of thumb: always open a fresh DB session inside background tasks; never\npass ORM objects from the request session into background tasks.\n",{"id":202,"difficulty":72,"q":203,"a":204},"periodic-tasks","How do you run a periodic\u002Fscheduled task in FastAPI?","**Option A — APScheduler** (in-process):\n```python\nfrom apscheduler.schedulers.asyncio import AsyncIOScheduler\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def lifespan(app):\n    scheduler = AsyncIOScheduler()\n    scheduler.add_job(cleanup_expired_tokens, \"interval\", minutes=30)\n    scheduler.start()\n    yield\n    scheduler.shutdown()\n\napp = FastAPI(lifespan=lifespan)\n```\n\n**Option B — ARQ cron jobs**:\n```python\nclass WorkerSettings:\n    cron_jobs = [\n        cron(cleanup_expired_tokens, minute={0, 30}),  # every 30 min\n    ]\n```\n\n**Option C — External scheduler** (cron, Kubernetes CronJob, AWS EventBridge):\nHits a `\u002Finternal\u002Fcleanup` endpoint at the scheduled time.\n\nRule of thumb: in-process schedulers (APScheduler) are convenient but tasks\nrun on every pod in a scaled deployment; use a single external scheduler or a\ndistributed lock to prevent duplicate execution.\n",{"id":206,"difficulty":35,"q":207,"a":208},"task-result","How do you return a task ID and let clients poll for the result in FastAPI?","The async job pattern: accept the request, enqueue the task, return a task ID;\nprovide a polling endpoint.\n\n```python\nimport uuid\nfrom arq import create_pool\n\n@app.post(\"\u002Freports\", status_code=202)\nasync def generate_report(request: Request, params: ReportParams):\n    task_id = str(uuid.uuid4())\n    await request.app.state.arq.enqueue_job(\"build_report\", task_id, params.dict())\n    return {\"task_id\": task_id, \"status\": \"pending\"}\n\n@app.get(\"\u002Freports\u002F{task_id}\")\nasync def get_report_status(task_id: str, request: Request):\n    job = await request.app.state.arq.job(task_id)\n    if job is None:\n        raise HTTPException(404)\n    status = await job.status()\n    if status == \"complete\":\n        result = await job.result()\n        return {\"status\": \"complete\", \"result\": result}\n    return {\"status\": status}\n```\n\nRule of thumb: return **202 Accepted** for async tasks — it signals that the\nrequest was accepted but processing isn't complete yet.\n",{"id":210,"difficulty":72,"q":211,"a":212},"background-task-memory-leak","What is a common memory leak pattern with FastAPI background tasks?","Capturing large objects in the background task's closure keeps them alive until\nthe task completes — even after the request is done.\n\n```python\n# BAD — the entire response object stays in memory during task\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    all_items = await db.get_all_items()   # large list\n    background_tasks.add_task(process_items, all_items)  # captured in closure\n    return item\n\n# GOOD — pass just the IDs; let the task load what it needs\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    new_item = await db.save(item)\n    background_tasks.add_task(process_new_item, new_item.id)  # just an int\n    return new_item\n```\n\nRule of thumb: pass primitive values (IDs, strings) to background tasks —\nnot large ORM results, response objects, or request streams.\n",{"description":32},"FastAPI background task interview questions — BackgroundTasks, task queues, Celery, ARQ, when to use each and common pitfalls.","fastapi\u002Fdeployment\u002Fbackground-tasks","-WcStUf5UsPUsZy-rQs45Aes9QrVEJgM5dHfqle9Ltg",1782244096269]