“FastAPI differentiates between async def and def handlers. async def functions execute directly on the main event loop (must never execute blocking CPU/I/O code). Standard def functions are automatically offloaded to an external worker ThreadPool (via AnyIO), safely isolating blocking database drivers (like psycopg2 or SQLAlchemy sync) from the event loop.”
How FastAPI automatically routes standard "def" functions to external AnyIO thread pools to avoid blocking the main event loop.
from fastapi import FastAPI
import asyncio
import time
app = FastAPI()
# 1. Non-blocking Async I/O (Main Event Loop)
@app.get("/async-io")
async def async_io():
await asyncio.sleep(1) # Suspends coroutine, 0 CPU wasted
return {"mode": "async_event_loop"}
# 2. Blocking Sync I/O (Auto-offloaded to ThreadPool)
@app.get("/sync-blocking")
def sync_blocking():
time.sleep(1) # ThreadPool worker sleeps safely; event loop unharmed
return {"mode": "thread_pool_worker"}FastAPI inspects function signature at startup using inspect.iscoroutinefunction()
If async def: FastAPI calls await endpoint() directly in the event loop
If standard def: FastAPI calls anyio.to_thread.run_sync(endpoint)
Worker thread pool executes blocking code without freezing other concurrent requests
Returns computed response back to the main ASGI event loop
Use async def with native async drivers (asyncpg, httpx, motor) for maximum 50,000+ req/sec non-blocking throughput.