[{"data":1,"prerenderedAt":1137},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002F":310,"surround-\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002F":1135},[4,96,212],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,30,42,54,66,90],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[35,36],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[47,48],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[59,60],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[95],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[101,104,122,140,158,176,194],{"title":102,"path":98,"stem":103},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":105,"path":106,"stem":107,"children":108},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[109,110,116],{"title":105,"path":106,"stem":107},{"title":111,"path":112,"stem":113,"children":114},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[115],{"title":111,"path":112,"stem":113},{"title":117,"path":118,"stem":119,"children":120},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[121],{"title":117,"path":118,"stem":119},{"title":123,"path":124,"stem":125,"children":126},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[127,128,134],{"title":123,"path":124,"stem":125},{"title":129,"path":130,"stem":131,"children":132},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[133],{"title":129,"path":130,"stem":131},{"title":135,"path":136,"stem":137,"children":138},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[139],{"title":135,"path":136,"stem":137},{"title":141,"path":142,"stem":143,"children":144},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[145,146,152],{"title":141,"path":142,"stem":143},{"title":147,"path":148,"stem":149,"children":150},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[151],{"title":147,"path":148,"stem":149},{"title":153,"path":154,"stem":155,"children":156},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[157],{"title":153,"path":154,"stem":155},{"title":159,"path":160,"stem":161,"children":162},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[163,164,170],{"title":159,"path":160,"stem":161},{"title":165,"path":166,"stem":167,"children":168},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[169],{"title":165,"path":166,"stem":167},{"title":171,"path":172,"stem":173,"children":174},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[175],{"title":171,"path":172,"stem":173},{"title":177,"path":178,"stem":179,"children":180},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[181,182,188],{"title":177,"path":178,"stem":179},{"title":183,"path":184,"stem":185,"children":186},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[187],{"title":183,"path":184,"stem":185},{"title":189,"path":190,"stem":191,"children":192},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[193],{"title":189,"path":190,"stem":191},{"title":195,"path":196,"stem":197,"children":198},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[199,200,206],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[217,220,232,250,268,280,292],{"title":218,"path":214,"stem":219},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":221,"path":222,"stem":223,"children":224},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[225,226],{"title":221,"path":222,"stem":223},{"title":227,"path":228,"stem":229,"children":230},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[231],{"title":227,"path":228,"stem":229},{"title":233,"path":234,"stem":235,"children":236},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[237,238,244],{"title":233,"path":234,"stem":235},{"title":239,"path":240,"stem":241,"children":242},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[243],{"title":239,"path":240,"stem":241},{"title":245,"path":246,"stem":247,"children":248},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[249],{"title":245,"path":246,"stem":247},{"title":251,"path":252,"stem":253,"children":254},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[255,256,262],{"title":251,"path":252,"stem":253},{"title":257,"path":258,"stem":259,"children":260},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[261],{"title":257,"path":258,"stem":259},{"title":263,"path":264,"stem":265,"children":266},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[267],{"title":263,"path":264,"stem":265},{"title":269,"path":270,"stem":271,"children":272},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[273,274],{"title":269,"path":270,"stem":271},{"title":275,"path":276,"stem":277,"children":278},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[279],{"title":275,"path":276,"stem":277},{"title":281,"path":282,"stem":283,"children":284},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[285,286],{"title":281,"path":282,"stem":283},{"title":287,"path":288,"stem":289,"children":290},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[291],{"title":287,"path":288,"stem":289},{"title":293,"path":294,"stem":295,"children":296},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[297,298,304],{"title":293,"path":294,"stem":295},{"title":299,"path":300,"stem":301,"children":302},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[303],{"title":299,"path":300,"stem":301},{"title":305,"path":306,"stem":307,"children":308},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[309],{"title":305,"path":306,"stem":307},{"id":311,"title":105,"body":312,"description":1104,"extension":1105,"meta":1106,"navigation":526,"path":106,"seo":1133,"stem":107,"__hash__":1134},"content\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex.md",{"type":313,"value":314,"toc":1095},"minimark",[315,319,332,351,483,488,500,638,650,654,657,748,752,755,842,846,856,860,863,1020,1024,1067,1071,1091],[316,317,105],"h1",{"id":318},"async-correctness-and-concurrency-in-fastapi",[320,321,322,323,327,328,331],"p",{},"Async correctness is the practice of keeping FastAPI's single-threaded event loop unblocked — choosing ",[324,325,326],"code",{},"async def"," versus ",[324,329,330],{},"def"," deliberately, never running synchronous blocking work on the loop, and bounding concurrency so the loop's freedom does not overwhelm downstreams.",[320,333,334,335,340,341,345,346,350],{},"This is the foundation of ",[336,337,339],"a",{"href":338},"\u002Fasync-background-tasks-observability\u002F","Async, Background Tasks and Observability",". Every other topic depends on it: ",[336,342,344],{"href":343},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002F","async database sessions"," exist to keep the most common blocking call off the loop, and ",[336,347,349],{"href":348},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002F","background task processing"," moves slow work off the request entirely.",[352,353,354,479],"figure",{},[355,356,364,365,364,369,364,373,364,380,364,388,364,394,364,398,364,404,364,409,364,414,364,419,364,425,364,430,364,434,364,437,364,442,364,446,364,450,364,453,364,457,364,462,364,464,364,467,364,470,364,474,364,476],"svg",{"viewBox":357,"role":358,"ariaLabelledBy":359,"xmlns":362,"style":363},"0 0 760 230","img",[360,361],"async-title","async-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;font-family:Inter,system-ui,sans-serif","\n  ",[366,367,368],"title",{"id":360},"Blocking the event loop versus offloading work",[370,371,372],"desc",{"id":361},"The top lane shows a blocking call inside an async handler freezing the event loop so queued requests wait. The bottom lane shows the blocking work offloaded to a thread pool, leaving the loop free to serve other requests.",[374,375,379],"text",{"x":376,"y":377,"style":378},"120","30","fill:#B23A3A;font-size:12px;font-weight:600;text-anchor:middle","Blocking on the loop",[381,382],"rect",{"x":383,"y":384,"width":385,"height":384,"rx":386,"style":387},"24","44","150","8","fill:#FFF4F4;stroke:#E08585;stroke-width:1.5px",[374,389,393],{"x":390,"y":391,"style":392},"99","71","fill:#B23A3A;font-size:11.5px;text-anchor:middle","async handler",[381,395],{"x":396,"y":384,"width":397,"height":384,"rx":386,"style":387},"200","160",[374,399,403],{"x":400,"y":401,"style":402},"280","66","fill:#B23A3A;font-size:11px;text-anchor:middle","sync call blocks",[374,405,408],{"x":400,"y":406,"style":407},"80","fill:#9A3030;font-size:10px;text-anchor:middle","loop frozen",[381,410],{"x":411,"y":384,"width":412,"height":384,"rx":386,"style":413},"386","180","fill:#F9FAFB;stroke:#D1D5DB;stroke-width:1.4px",[374,415,418],{"x":416,"y":391,"style":417},"476","fill:#6B7280;font-size:11px;text-anchor:middle","other requests wait",[420,421],"line",{"x1":422,"y1":401,"x2":423,"y2":401,"style":424},"174","198","stroke:#B23A3A;stroke-width:1.6px",[426,427],"polygon",{"points":428,"style":429},"198,62 206,66 198,70","fill:#B23A3A",[420,431],{"x1":432,"y1":401,"x2":433,"y2":401,"style":424},"360","384",[426,435],{"points":436,"style":429},"384,62 392,66 384,70",[374,438,441],{"x":376,"y":439,"style":440},"138","fill:#00695C;font-size:12px;font-weight:600;text-anchor:middle","Offloaded",[381,443],{"x":383,"y":444,"width":385,"height":384,"rx":386,"style":445},"152","fill:#E0F2F1;stroke:#009688;stroke-width:1.6px",[374,447,393],{"x":390,"y":448,"style":449},"179","fill:#00695C;font-size:11.5px;text-anchor:middle",[381,451],{"x":396,"y":444,"width":397,"height":384,"rx":386,"style":452},"fill:#FFFFFF;stroke:#4DB6AC;stroke-width:1.5px",[374,454,456],{"x":400,"y":422,"style":455},"fill:#00695C;font-size:11px;text-anchor:middle","await to_thread",[374,458,461],{"x":400,"y":459,"style":460},"188","fill:#6B7280;font-size:10px;text-anchor:middle","runs off-loop",[381,463],{"x":411,"y":444,"width":412,"height":384,"rx":386,"style":445},[374,465,466],{"x":416,"y":448,"style":455},"loop serves others",[420,468],{"x1":422,"y1":422,"x2":423,"y2":422,"style":469},"stroke:#00796B;stroke-width:1.6px",[426,471],{"points":472,"style":473},"198,170 206,174 198,178","fill:#00796B",[420,475],{"x1":432,"y1":422,"x2":433,"y2":422,"style":469},[426,477],{"points":478,"style":473},"384,170 392,174 384,178",[480,481,482],"figcaption",{},"A synchronous call inside an async handler freezes the loop for everyone; offloading it to a thread lets the loop keep serving other requests.",[484,485,487],"h2",{"id":486},"core-mechanics-one-loop-per-worker","Core Mechanics: One Loop per Worker",[320,489,490,491,494,495,327,497,499],{},"A FastAPI worker runs one event loop on one thread. Concurrency comes from coroutines voluntarily yielding at ",[324,492,493],{},"await"," points so the loop can advance another. A synchronous call never yields, so while it runs the loop is frozen and no other request progresses. This is why the ",[324,496,326],{},[324,498,330],{}," choice is about correctness, not style.",[501,502,507],"pre",{"className":503,"code":504,"language":505,"meta":506,"style":506},"language-python shiki shiki-themes github-light","import httpx\n\n\n@app.get(\"\u002Fupstream\")\nasync def upstream() -> dict:\n    # Correct: an async client yields at every await, keeping the loop free.\n    async with httpx.AsyncClient() as client:\n        resp = await client.get(\"https:\u002F\u002Fapi.example.com\u002Fdata\", timeout=5.0)\n    return resp.json()\n","python","",[324,508,509,521,528,533,550,572,579,597,629],{"__ignoreMap":506},[510,511,513,517],"span",{"class":420,"line":512},1,[510,514,516],{"class":515},"sD7c4","import",[510,518,520],{"class":519},"sgsFI"," httpx\n",[510,522,524],{"class":420,"line":523},2,[510,525,527],{"emptyLinePlaceholder":526},true,"\n",[510,529,531],{"class":420,"line":530},3,[510,532,527],{"emptyLinePlaceholder":526},[510,534,536,540,543,547],{"class":420,"line":535},4,[510,537,539],{"class":538},"s7eDp","@app.get",[510,541,542],{"class":519},"(",[510,544,546],{"class":545},"sYBdl","\"\u002Fupstream\"",[510,548,549],{"class":519},")\n",[510,551,553,556,559,562,565,569],{"class":420,"line":552},5,[510,554,555],{"class":515},"async",[510,557,558],{"class":515}," def",[510,560,561],{"class":538}," upstream",[510,563,564],{"class":519},"() -> ",[510,566,568],{"class":567},"sYu0t","dict",[510,570,571],{"class":519},":\n",[510,573,575],{"class":420,"line":574},6,[510,576,578],{"class":577},"sAwPA","    # Correct: an async client yields at every await, keeping the loop free.\n",[510,580,582,585,588,591,594],{"class":420,"line":581},7,[510,583,584],{"class":515},"    async",[510,586,587],{"class":515}," with",[510,589,590],{"class":519}," httpx.AsyncClient() ",[510,592,593],{"class":515},"as",[510,595,596],{"class":519}," client:\n",[510,598,600,603,606,609,612,615,618,622,624,627],{"class":420,"line":599},8,[510,601,602],{"class":519},"        resp ",[510,604,605],{"class":515},"=",[510,607,608],{"class":515}," await",[510,610,611],{"class":519}," client.get(",[510,613,614],{"class":545},"\"https:\u002F\u002Fapi.example.com\u002Fdata\"",[510,616,617],{"class":519},", ",[510,619,621],{"class":620},"sqxcx","timeout",[510,623,605],{"class":515},[510,625,626],{"class":567},"5.0",[510,628,549],{"class":519},[510,630,632,635],{"class":420,"line":631},9,[510,633,634],{"class":515},"    return",[510,636,637],{"class":519}," resp.json()\n",[320,639,640,641,643,644,646,647,649],{},"If a handler is genuinely synchronous, declaring it ",[324,642,330],{}," is safe — FastAPI runs ",[324,645,330],{}," handlers in a thread pool, so their blocking does not touch the loop. The trap is the hybrid: an ",[324,648,326],{}," handler that calls blocking code.",[484,651,653],{"id":652},"production-implementation-offloading-blocking-work","Production Implementation: Offloading Blocking Work",[320,655,656],{},"When you must call blocking code from an async handler — a legacy sync driver, a CPU-bound computation — offload it. Use a thread for blocking I\u002FO and a process for CPU-bound work.",[501,658,660],{"className":503,"code":659,"language":505,"meta":506,"style":506},"import anyio\n\n\n@app.post(\"\u002Fthumbnail\")\nasync def thumbnail(image: bytes) -> dict[str, int]:\n    # CPU-bound resize offloaded so the loop is not frozen during it.\n    size = await anyio.to_thread.run_sync(resize_image, image)\n    return {\"bytes\": size}\n",[324,661,662,669,673,677,689,718,723,735],{"__ignoreMap":506},[510,663,664,666],{"class":420,"line":512},[510,665,516],{"class":515},[510,667,668],{"class":519}," anyio\n",[510,670,671],{"class":420,"line":523},[510,672,527],{"emptyLinePlaceholder":526},[510,674,675],{"class":420,"line":530},[510,676,527],{"emptyLinePlaceholder":526},[510,678,679,682,684,687],{"class":420,"line":535},[510,680,681],{"class":538},"@app.post",[510,683,542],{"class":519},[510,685,686],{"class":545},"\"\u002Fthumbnail\"",[510,688,549],{"class":519},[510,690,691,693,695,698,701,704,707,710,712,715],{"class":420,"line":552},[510,692,555],{"class":515},[510,694,558],{"class":515},[510,696,697],{"class":538}," thumbnail",[510,699,700],{"class":519},"(image: ",[510,702,703],{"class":567},"bytes",[510,705,706],{"class":519},") -> dict[",[510,708,709],{"class":567},"str",[510,711,617],{"class":519},[510,713,714],{"class":567},"int",[510,716,717],{"class":519},"]:\n",[510,719,720],{"class":420,"line":574},[510,721,722],{"class":577},"    # CPU-bound resize offloaded so the loop is not frozen during it.\n",[510,724,725,728,730,732],{"class":420,"line":581},[510,726,727],{"class":519},"    size ",[510,729,605],{"class":515},[510,731,608],{"class":515},[510,733,734],{"class":519}," anyio.to_thread.run_sync(resize_image, image)\n",[510,736,737,739,742,745],{"class":420,"line":599},[510,738,634],{"class":515},[510,740,741],{"class":519}," {",[510,743,744],{"class":545},"\"bytes\"",[510,746,747],{"class":519},": size}\n",[484,749,751],{"id":750},"controlling-concurrency","Controlling Concurrency",[320,753,754],{},"An unblocked loop can issue many concurrent operations, which can overwhelm a downstream. Bound it with a semaphore sized to the dependency's capacity.",[501,756,758],{"className":503,"code":757,"language":505,"meta":506,"style":506},"import asyncio\n\n# At most 10 concurrent calls to the downstream, regardless of request volume.\n_limit = asyncio.Semaphore(10)\n\n\nasync def fetch(client: httpx.AsyncClient, url: str) -> bytes:\n    async with _limit:\n        return (await client.get(url)).content\n",[324,759,760,767,771,776,791,795,799,820,829],{"__ignoreMap":506},[510,761,762,764],{"class":420,"line":512},[510,763,516],{"class":515},[510,765,766],{"class":519}," asyncio\n",[510,768,769],{"class":420,"line":523},[510,770,527],{"emptyLinePlaceholder":526},[510,772,773],{"class":420,"line":530},[510,774,775],{"class":577},"# At most 10 concurrent calls to the downstream, regardless of request volume.\n",[510,777,778,781,783,786,789],{"class":420,"line":535},[510,779,780],{"class":519},"_limit ",[510,782,605],{"class":515},[510,784,785],{"class":519}," asyncio.Semaphore(",[510,787,788],{"class":567},"10",[510,790,549],{"class":519},[510,792,793],{"class":420,"line":552},[510,794,527],{"emptyLinePlaceholder":526},[510,796,797],{"class":420,"line":574},[510,798,527],{"emptyLinePlaceholder":526},[510,800,801,803,805,808,811,813,816,818],{"class":420,"line":581},[510,802,555],{"class":515},[510,804,558],{"class":515},[510,806,807],{"class":538}," fetch",[510,809,810],{"class":519},"(client: httpx.AsyncClient, url: ",[510,812,709],{"class":567},[510,814,815],{"class":519},") -> ",[510,817,703],{"class":567},[510,819,571],{"class":519},[510,821,822,824,826],{"class":420,"line":599},[510,823,584],{"class":515},[510,825,587],{"class":515},[510,827,828],{"class":519}," _limit:\n",[510,830,831,834,837,839],{"class":420,"line":631},[510,832,833],{"class":515},"        return",[510,835,836],{"class":519}," (",[510,838,493],{"class":515},[510,840,841],{"class":519}," client.get(url)).content\n",[484,843,845],{"id":844},"async-and-performance-notes","Async and Performance Notes",[320,847,848,849,851,852,855],{},"The thread pool that runs ",[324,850,330],{}," handlers and ",[324,853,854],{},"to_thread"," work is finite; saturating it with long blocking calls reintroduces queuing. Size CPU-bound work to a process pool, keep thread-offloaded work I\u002FO-bound, and prefer truly async libraries on the hot path so offloading is the exception, not the rule.",[484,857,859],{"id":858},"testing-strategy","Testing Strategy",[320,861,862],{},"Detect accidental blocking by asserting that concurrent requests actually overlap:",[501,864,866],{"className":503,"code":865,"language":505,"meta":506,"style":506},"import asyncio\nimport time\n\nimport httpx\n\n\nasync def test_requests_run_concurrently(app):\n    # Two 100ms async endpoints should finish in ~100ms, not ~200ms, if non-blocking.\n    transport = httpx.ASGITransport(app=app)\n    async with httpx.AsyncClient(transport=transport, base_url=\"http:\u002F\u002Ft\") as c:\n        start = time.perf_counter()\n        await asyncio.gather(c.get(\"\u002Fslow\"), c.get(\"\u002Fslow\"))\n        assert time.perf_counter() - start \u003C 0.18\n",[324,867,868,874,881,885,891,895,899,911,916,934,968,979,999],{"__ignoreMap":506},[510,869,870,872],{"class":420,"line":512},[510,871,516],{"class":515},[510,873,766],{"class":519},[510,875,876,878],{"class":420,"line":523},[510,877,516],{"class":515},[510,879,880],{"class":519}," time\n",[510,882,883],{"class":420,"line":530},[510,884,527],{"emptyLinePlaceholder":526},[510,886,887,889],{"class":420,"line":535},[510,888,516],{"class":515},[510,890,520],{"class":519},[510,892,893],{"class":420,"line":552},[510,894,527],{"emptyLinePlaceholder":526},[510,896,897],{"class":420,"line":574},[510,898,527],{"emptyLinePlaceholder":526},[510,900,901,903,905,908],{"class":420,"line":581},[510,902,555],{"class":515},[510,904,558],{"class":515},[510,906,907],{"class":538}," test_requests_run_concurrently",[510,909,910],{"class":519},"(app):\n",[510,912,913],{"class":420,"line":599},[510,914,915],{"class":577},"    # Two 100ms async endpoints should finish in ~100ms, not ~200ms, if non-blocking.\n",[510,917,918,921,923,926,929,931],{"class":420,"line":631},[510,919,920],{"class":519},"    transport ",[510,922,605],{"class":515},[510,924,925],{"class":519}," httpx.ASGITransport(",[510,927,928],{"class":620},"app",[510,930,605],{"class":515},[510,932,933],{"class":519},"app)\n",[510,935,937,939,941,944,947,949,952,955,957,960,963,965],{"class":420,"line":936},10,[510,938,584],{"class":515},[510,940,587],{"class":515},[510,942,943],{"class":519}," httpx.AsyncClient(",[510,945,946],{"class":620},"transport",[510,948,605],{"class":515},[510,950,951],{"class":519},"transport, ",[510,953,954],{"class":620},"base_url",[510,956,605],{"class":515},[510,958,959],{"class":545},"\"http:\u002F\u002Ft\"",[510,961,962],{"class":519},") ",[510,964,593],{"class":515},[510,966,967],{"class":519}," c:\n",[510,969,971,974,976],{"class":420,"line":970},11,[510,972,973],{"class":519},"        start ",[510,975,605],{"class":515},[510,977,978],{"class":519}," time.perf_counter()\n",[510,980,982,985,988,991,994,996],{"class":420,"line":981},12,[510,983,984],{"class":515},"        await",[510,986,987],{"class":519}," asyncio.gather(c.get(",[510,989,990],{"class":545},"\"\u002Fslow\"",[510,992,993],{"class":519},"), c.get(",[510,995,990],{"class":545},[510,997,998],{"class":519},"))\n",[510,1000,1002,1005,1008,1011,1014,1017],{"class":420,"line":1001},13,[510,1003,1004],{"class":515},"        assert",[510,1006,1007],{"class":519}," time.perf_counter() ",[510,1009,1010],{"class":515},"-",[510,1012,1013],{"class":519}," start ",[510,1015,1016],{"class":515},"\u003C",[510,1018,1019],{"class":567}," 0.18\n",[484,1021,1023],{"id":1022},"failure-modes-and-debugging","Failure Modes and Debugging",[1025,1026,1027,1035,1047,1057],"ul",{},[1028,1029,1030,1034],"li",{},[1031,1032,1033],"strong",{},"Hidden blocking in libraries."," A dependency may block internally; profile and offload it.",[1028,1036,1037,1040,1041,1043,1044,1046],{},[1031,1038,1039],{},"Thread-pool exhaustion."," Too many long ",[324,1042,330],{}," or ",[324,1045,854],{}," calls starve the pool; move CPU work to processes.",[1028,1048,1049,1052,1053,1056],{},[1031,1050,1051],{},"Unbounded fan-out."," ",[324,1054,1055],{},"asyncio.gather"," over thousands of tasks opens thousands of connections; bound with a semaphore.",[1028,1058,1059,1062,1063,1066],{},[1031,1060,1061],{},"Sync database drivers."," The classic culprit; switch to async sessions, covered in ",[336,1064,1065],{"href":343},"Async Database Sessions",".",[484,1068,1070],{"id":1069},"related-reading","Related Reading",[1025,1072,1073,1080],{},[1028,1074,1075,1052,1078,1066],{},[1031,1076,1077],{},"Up to the section:",[336,1079,339],{"href":338},[1028,1081,1082,1052,1085,1087,1088,1066],{},[1031,1083,1084],{},"Composes with:",[336,1086,1065],{"href":343}," and ",[336,1089,1090],{"href":348},"Background Task Processing",[1092,1093,1094],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":506,"searchDepth":523,"depth":523,"links":1096},[1097,1098,1099,1100,1101,1102,1103],{"id":486,"depth":523,"text":487},{"id":652,"depth":523,"text":653},{"id":750,"depth":523,"text":751},{"id":844,"depth":523,"text":845},{"id":858,"depth":523,"text":859},{"id":1022,"depth":523,"text":1023},{"id":1069,"depth":523,"text":1070},"Write correct async FastAPI code: when to use async def vs def, why blocking calls stall the event loop, offloading with run_in_threadpool and anyio, and controlling concurrency.","md",{"slug":1107,"type":1108,"breadcrumb":1109,"datePublished":1118,"dateModified":1119,"faq":1120},"async-correctness-concurrency","cluster",[1110,1113,1115],{"label":1111,"path":1112},"Home","\u002F",{"label":1114,"path":338},"Async, Background Tasks & Observability",{"label":1116,"path":1117},"Async Correctness & Concurrency","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002F","2026-02-11","2026-06-18",[1121,1124,1127,1130],{"q":1122,"a":1123},"Should my route be async def or plain def?","Use async def when the handler awaits async I\u002FO such as an async database or HTTP client. Use plain def when the work is synchronous and blocking, because FastAPI runs def handlers in a thread pool where blocking is safe. The mistake to avoid is an async def handler that performs synchronous blocking work, which stalls the event loop.",{"q":1125,"a":1126},"Why does one blocking call slow down unrelated requests?","Each worker runs a single event loop on one thread. While that thread is blocked inside a synchronous call, it cannot advance any other coroutine, so every concurrent request on that worker waits. The loop only achieves concurrency when coroutines yield at await points, which blocking calls never do.",{"q":1128,"a":1129},"How do I run blocking code without blocking the loop?","Offload it to a thread with anyio.to_thread.run_sync or Starlette's run_in_threadpool for I\u002FO-bound blocking calls, and to a process pool for CPU-bound work. The coroutine awaits the offloaded result, so the loop stays free to serve other requests while the work runs elsewhere.",{"q":1131,"a":1132},"How do I limit concurrency so I do not overwhelm a downstream service?","Guard concurrent work with an asyncio.Semaphore sized to the downstream's capacity, or use a task group with a bounded limiter. This caps how many operations run at once, preventing a burst of requests from opening more connections than the dependency can handle.",{"title":105,"description":1104},"FqgXIICqWtg0MxUv5RlaKCdgof2h9QB_7BHioP5oYWo",[1136,1136],null,1781809863153]