[{"data":1,"prerenderedAt":111},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Ffundamentals\u002Fasync-basics":3},{"page":4,"siblings":94,"blog":86},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":6,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Ffastapi\u002Ffundamentals\u002Fasync-basics.md","Async Basics",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","FastAPI","fastapi",{},true,1,"\u002Ffastapi\u002Ffundamentals\u002Fasync-basics",[23,28,32,36,40,44,49,53,57,61,65,69,73,77,81],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-async-await","easy","What is async\u002Fawait in Python and why does FastAPI use it?","`async`\u002F`await` is Python's syntax for **cooperative concurrency**. An `async def`\nfunction returns a **coroutine** object; `await` suspends that coroutine until the\nawaited operation completes, letting the event loop run other coroutines in the\nmeantime. No thread switch happens — the same OS thread handles many requests.\n\n```python\nimport asyncio\n\nasync def fetch_user(user_id: int) -> dict:\n    await asyncio.sleep(0.1)   # yield control while \"waiting\"\n    return {\"id\": user_id}\n```\n\nFastAPI is built on top of **Starlette** and **asyncio**. When you declare a route\nhandler as `async def`, FastAPI runs it directly on the event loop, giving you\nhigh concurrency without spawning new threads for each request.\n\nRule of thumb: use `async def` handlers whenever you `await` something (DB call,\nHTTP request, cache); use `def` for pure CPU work that has no async alternative.\n",{"id":29,"difficulty":14,"q":30,"a":31},"asgi-vs-wsgi","What is the difference between ASGI and WSGI?","**WSGI** (Web Server Gateway Interface, PEP 3333) is the classic Python web\nserver standard. It assumes a **synchronous**, request-per-thread model —\na server calls `app(environ, start_response)` and blocks until the response is\nreturned. Frameworks like Flask and Django (pre-4.x) use WSGI.\n\n**ASGI** (Asynchronous Server Gateway Interface) extends WSGI to support\n**async handlers, WebSockets and long-polling** in the same interface. A server\ncalls `await app(scope, receive, send)` and the app may `await` I\u002FO freely.\n\n```python\n# Minimal ASGI app (what Starlette\u002FFastAPI is under the hood)\nasync def app(scope, receive, send):\n    if scope[\"type\"] == \"http\":\n        await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n        await send({\"type\": \"http.response.body\", \"body\": b\"Hello\"})\n```\n\nFastAPI is an ASGI framework served by **Uvicorn** (or Hypercorn). ASGI lets a\nsingle process handle thousands of simultaneous connections without one thread\nper connection.\n\nRule of thumb: WSGI = sync\u002Fthreads; ASGI = async\u002Fevent-loop; FastAPI requires ASGI.\n",{"id":33,"difficulty":14,"q":34,"a":35},"event-loop","What is the Python event loop and how does FastAPI interact with it?","The **event loop** is a scheduler that runs coroutines, handles I\u002FO callbacks\nand timers — all in a single thread. When a coroutine `await`s an I\u002FO operation,\nthe loop suspends it and runs the next ready coroutine.\n\n```python\nimport asyncio\n\nasync def task_a():\n    print(\"A start\")\n    await asyncio.sleep(1)   # loop runs task_b here\n    print(\"A done\")\n\nasync def task_b():\n    print(\"B start\")\n    await asyncio.sleep(0.5)\n    print(\"B done\")\n\nasyncio.run(asyncio.gather(task_a(), task_b()))\n# prints: A start, B start, B done, A done\n```\n\nUvicorn creates one event loop per worker process and passes every incoming HTTP\nrequest into it. FastAPI schedules your `async def` handler on that loop. If you\nblock the loop (e.g., call `time.sleep()` or a synchronous DB driver), *all*\nrequests stall until the block clears.\n\nRule of thumb: never call blocking code directly from an `async def` handler —\nuse `await asyncio.to_thread()` or a thread pool executor instead.\n",{"id":37,"difficulty":25,"q":38,"a":39},"coroutine-vs-function","What is a coroutine and how is it different from a regular function?","A **coroutine** is created by an `async def` function. Calling it does **not**\nexecute the body — it returns a coroutine object. The body only runs when you\n`await` it (or pass it to `asyncio.run()`).\n\n```python\ndef regular():\n    return 42          # runs immediately on call\n\nasync def coro():\n    return 42          # returns \u003Ccoroutine object> on call\n\nresult = regular()     # 42\nobj    = coro()        # \u003Ccoroutine object coro at 0x...> — nothing ran yet\nresult = await coro()  # 42 — body now runs\n```\n\nFastAPI recognises `async def` route handlers and schedules them with `await`\ninternally. If you declare a route as `def`, FastAPI runs it in a thread pool\nto avoid blocking the event loop.\n\nRule of thumb: a coroutine is a pauseable function; without `await` (or\n`asyncio.run`), its body never executes.\n",{"id":41,"difficulty":14,"q":42,"a":43},"sync-vs-async-handler","When should you use `def` vs `async def` for a FastAPI route handler?","| Handler | When to use | FastAPI runs it in |\n|---------|-------------|--------------------|\n| `async def` | calls `await`-able I\u002FO (DB, HTTP, cache) | event loop directly |\n| `def` | CPU work or sync libraries (Pandas, Pillow) | thread-pool executor |\n\n```python\n# async — awaits the DB; loop stays free\n@app.get(\"\u002Fusers\u002F{id}\")\nasync def get_user(id: int, db: AsyncSession = Depends(get_db)):\n    return await db.get(User, id)\n\n# def — pandas is sync-only; FastAPI offloads to a thread\n@app.post(\"\u002Freport\")\ndef generate_report(payload: ReportRequest):\n    df = pd.read_csv(payload.path)   # blocking I\u002FO, but in a thread\n    return df.describe().to_dict()\n```\n\nThe wrong choice either wastes threads (`async def` + sync library that blocks)\nor adds unnecessary thread overhead (`def` + code that could have been `await`ed).\n\nRule of thumb: if you `await` anything inside the handler, declare it `async def`;\nif the handler is pure CPU\u002Fsync, use `def`.\n",{"id":45,"difficulty":46,"q":47,"a":48},"blocking-event-loop","hard","What happens if you block the event loop in FastAPI, and how do you fix it?","Blocking the event loop means calling any operation that occupies the thread\nwithout yielding: `time.sleep()`, synchronous file reads, `requests.get()`, heavy\ncomputation. While blocked, **no other coroutine runs** — all concurrent requests\nstall until the block clears.\n\n```python\n# BAD — blocks the event loop for every request\n@app.get(\"\u002Fslow\")\nasync def slow():\n    time.sleep(2)          # freezes ALL requests for 2 s\n    return {\"ok\": True}\n\n# GOOD — moves the blocking call to a thread\n@app.get(\"\u002Fslow\")\nasync def slow():\n    await asyncio.to_thread(time.sleep, 2)   # only this coroutine pauses\n    return {\"ok\": True}\n```\n\nOther fixes:\n- Switch to an async library (`httpx` instead of `requests`, `asyncpg` instead of `psycopg2`).\n- Use `loop.run_in_executor(None, blocking_fn)` (older equivalent of `asyncio.to_thread`).\n- Declare the handler as plain `def` — FastAPI automatically runs it in a thread pool.\n\nRule of thumb: if you can't avoid a blocking call inside `async def`, use\n`asyncio.to_thread()` to push it off the event loop.\n",{"id":50,"difficulty":14,"q":51,"a":52},"asyncio-gather","What does `asyncio.gather()` do and when would you use it in FastAPI?","`asyncio.gather(*coros)` runs multiple coroutines **concurrently on the same\nevent loop** and returns their results as a list in the same order as the inputs.\nIt's the idiomatic way to fan-out I\u002FO work within a single handler.\n\n```python\n@app.get(\"\u002Fdashboard\u002F{user_id}\")\nasync def dashboard(user_id: int):\n    # fire both DB queries at the same time instead of sequentially\n    user, orders = await asyncio.gather(\n        fetch_user(user_id),\n        fetch_orders(user_id),\n    )\n    return {\"user\": user, \"orders\": orders}\n```\n\nSequential awaits would take `t_user + t_orders`; `gather` takes `max(t_user, t_orders)`.\n\nRule of thumb: use `asyncio.gather()` whenever two or more I\u002FO calls are\nindependent — it cuts latency to the slowest one instead of the sum.\n",{"id":54,"difficulty":46,"q":55,"a":56},"anyio-trio","What is anyio and how does it relate to FastAPI?","**anyio** is a compatibility shim that lets library authors write async code once\nand run it on **asyncio** *or* **Trio** without changes. Starlette (FastAPI's\nfoundation) adopted anyio as its async backend in v0.20+.\n\n```python\nimport anyio\n\nasync def run_parallel():\n    async with anyio.create_task_group() as tg:\n        tg.start_soon(task_a)\n        tg.start_soon(task_b)\n```\n\nIn practice for FastAPI users:\n- You still write plain `asyncio` code — anyio is transparent.\n- `pytest-anyio` \u002F `anyio.from_thread.run_sync` become relevant when testing.\n- Starlette's `BackgroundTask` and `lifespan` use anyio task groups internally.\n\nRule of thumb: you rarely call anyio directly in application code; knowing it\nexists explains why `pytest-anyio` is the recommended test runner for Starlette apps.\n",{"id":58,"difficulty":14,"q":59,"a":60},"run-sync-in-async","How do you call a synchronous function from an async context without blocking?","Use `asyncio.to_thread()` (Python 3.9+) to run a sync function in the default\nthread-pool executor while keeping the event loop free.\n\n```python\nimport asyncio\n\ndef sync_heavy(n: int) -> int:\n    # CPU or blocking I\u002FO, e.g. reading a large file\n    return sum(range(n))\n\n@app.get(\"\u002Fcompute\")\nasync def compute(n: int):\n    result = await asyncio.to_thread(sync_heavy, n)\n    return {\"result\": result}\n```\n\nFor older Python (3.7–3.8) use `loop.run_in_executor(None, fn, *args)`.\nFor FastAPI specifically, declaring the route as plain `def` achieves the same\nthing automatically — FastAPI calls `run_in_executor` for you.\n\nRule of thumb: prefer `def` route handlers for wholly-sync work; use\n`asyncio.to_thread()` only when you're already in an `async def` and need\none sync call inside otherwise-async logic.\n",{"id":62,"difficulty":14,"q":63,"a":64},"httpx-vs-requests","Why use httpx instead of requests in a FastAPI application?","`requests` is **synchronous** — calling `requests.get()` blocks the OS thread\nuntil the response arrives. In an `async def` handler that means the event loop\nis frozen for the duration of every outbound HTTP call.\n\n`httpx` provides an identical API *plus* an `AsyncClient` that integrates with\nasyncio:\n\n```python\nimport httpx\n\n@app.get(\"\u002Fproxy\")\nasync def proxy(url: str):\n    async with httpx.AsyncClient() as client:\n        resp = await client.get(url)   # loop stays free\n    return resp.json()\n```\n\n`httpx` also supports HTTP\u002F2, connection pooling via a shared client, and\n`httpx.AsyncClient` as a test transport for FastAPI's `TestClient`.\n\nRule of thumb: always use `httpx.AsyncClient` for outbound HTTP inside FastAPI\nasync handlers; use `requests` only in plain `def` routes or CLI scripts.\n",{"id":66,"difficulty":25,"q":67,"a":68},"what-is-starlette","What is Starlette and what does FastAPI add on top of it?","**Starlette** is a lightweight ASGI framework\u002Ftoolkit that provides routing,\nmiddleware, WebSockets, background tasks and static files. FastAPI is built\n**directly on top of Starlette** — every `FastAPI()` instance is a Starlette app.\n\nFastAPI's additions:\n- **Type-hint-driven parameter parsing** — path, query, body extracted via annotations.\n- **Pydantic v2 validation** — automatic request validation and serialization.\n- **Dependency injection** via `Depends()`.\n- **Automatic OpenAPI \u002F JSON Schema** generation (Swagger UI, ReDoc).\n- `HTTPException` helpers and response model enforcement.\n\n```python\nfrom fastapi import FastAPI\nfrom starlette.applications import Starlette   # same base class\n```\n\nRule of thumb: think of FastAPI as Starlette + Pydantic + OpenAPI; you can use\nall Starlette primitives (middleware, WebSockets, mounts) in a FastAPI app.\n",{"id":70,"difficulty":46,"q":71,"a":72},"concurrency-model","Describe FastAPI's concurrency model end-to-end.","1. **Uvicorn** (ASGI server) runs one event loop per worker process.\n2. A new HTTP connection arrives; Uvicorn's event loop accepts it and parses headers.\n3. FastAPI's router matches the URL and calls the handler.\n4. If the handler is `async def` → FastAPI `await`s it on the event loop.\n   If the handler is `def` → FastAPI submits it to `anyio`'s thread pool and\n   `await`s the future, keeping the event loop free.\n5. The handler performs I\u002FO (e.g., `await db.execute()`). The coroutine suspends\n   and the loop picks up the next ready coroutine.\n6. When I\u002FO completes, the handler resumes and returns a response dict.\n7. FastAPI serializes it (Pydantic `model_dump` → JSON) and sends bytes back\n   through Uvicorn.\n\n```\nrequest → Uvicorn → FastAPI router → async handler (event loop)\n                                   or sync handler (thread pool)\n                                   → Pydantic serialize → response\n```\n\nRule of thumb: one worker process = one event loop = many concurrent requests,\nbut only one coroutine actually executes CPU instructions at a time.\n",{"id":74,"difficulty":14,"q":75,"a":76},"asyncio-task","What is `asyncio.create_task()` and how does it differ from `await`?","`asyncio.create_task(coro)` schedules a coroutine to run **concurrently** on the\nevent loop and returns a `Task` object immediately. The task starts running as\nsoon as the current coroutine yields. `await coro` runs the coroutine\n**sequentially** — the caller suspends until it finishes.\n\n```python\nasync def handler():\n    # sequential: total time = t_a + t_b\n    result_a = await slow_query_a()\n    result_b = await slow_query_b()\n\n    # concurrent: total time = max(t_a, t_b)\n    task_a = asyncio.create_task(slow_query_a())\n    task_b = asyncio.create_task(slow_query_b())\n    result_a = await task_a\n    result_b = await task_b\n```\n\n`asyncio.gather(*coros)` is usually more convenient than manual `create_task`\nwhen you want results; `create_task` is useful when you want to start a task\nand do other work before collecting its result.\n\nRule of thumb: `create_task` is fire-and-partially-forget; use `gather` when you\nneed all results in one call.\n",{"id":78,"difficulty":46,"q":79,"a":80},"thread-pool-size","How does FastAPI\u002Fanyio determine the size of its thread pool for sync handlers?","FastAPI delegates sync (`def`) handlers to **anyio**'s default thread limiter,\nwhich caps concurrent threads at **40 by default** (per worker process). This\nprevents unbounded thread creation from swamping the OS.\n\n```python\nimport anyio\n\n# Check or raise the cap at startup\n@app.on_event(\"startup\")\nasync def configure_threads():\n    limiter = anyio.from_thread.current_default_thread_limiter()\n    limiter.total_tokens = 20   # reduce if each thread uses lots of memory\n```\n\nThe 40-thread default is conservative for most workloads. If your sync handlers\ndo fast CPU work, 40 is plenty. If they block on slow external calls, consider\nusing `async def` + `asyncio.to_thread()` instead so you control the pool.\n\nRule of thumb: for heavily sync-bound FastAPI apps, tune the anyio thread limit or,\nbetter, switch to async libraries so you don't need threads at all.\n",{"id":82,"difficulty":14,"q":83,"a":84},"lifespan-startup-shutdown","What is the difference between `@app.on_event(\"startup\")` and `lifespan`?","`@app.on_event(\"startup\u002Fshutdown\")` decorators are the **legacy** approach —\nthey work but are deprecated in recent FastAPI. The modern replacement is the\n**`lifespan` context manager**, which groups startup and shutdown in one function\nusing `yield`.\n\n```python\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # startup\n    await db_pool.connect()\n    yield\n    # shutdown\n    await db_pool.disconnect()\n\napp = FastAPI(lifespan=lifespan)\n```\n\nBenefits of `lifespan`:\n- Single function instead of two decorators.\n- Exception in startup cleanly skips the yield and shutdown block won't be reached.\n- Composable — you can call other async context managers inside.\n- Testable with `@asynccontextmanager` directly.\n\nRule of thumb: use `lifespan=` for all new FastAPI apps; avoid `@app.on_event`.\n",15,null,{"description":11},"FastAPI async\u002Fawait interview questions — coroutines, event loop, ASGI vs WSGI, sync vs async route handlers and avoiding blocking the event loop.","fastapi\u002Ffundamentals\u002Fasync-basics","Fundamentals","fundamentals","2026-06-20","n2u_aF57BBwUZZehz50R-u1kpVdckwYf2HwJHCctb6c",[95,96,99,103,107],{"subtopic":6,"path":21,"order":20},{"subtopic":97,"path":98,"order":12},"Request Lifecycle","\u002Ffastapi\u002Ffundamentals\u002Frequest-lifecycle",{"subtopic":100,"path":101,"order":102},"Path Operations","\u002Ffastapi\u002Ffundamentals\u002Fpath-operations",3,{"subtopic":104,"path":105,"order":106},"Type Hints & FastAPI","\u002Ffastapi\u002Ffundamentals\u002Ftype-hints",4,{"subtopic":108,"path":109,"order":110},"OpenAPI & Docs","\u002Ffastapi\u002Ffundamentals\u002Fopenapi-docs",5,1782244112510]