async def vs def - when to use which
CoreI/O you can await: DB queries, HTTP calls, file reads, caching.
Blocking libraries with no async version. FastAPI runs these in a thread pool.
# USE async def when doing awaited I/O: DB, Redis, HTTP calls
@app.get("/users")
async def list_users(db = Depends(get_db)):
result = await db.execute(select(User)) # awaited I/O
return result.scalars().all()
# USE def for blocking/sync libraries
# FastAPI runs these in a thread pool automatically
@app.get("/report")
def report():
return {"result": blocking_library_call()}
# Run blocking code from an async route
from fastapi.concurrency import run_in_threadpool
@app.post("/process")
async def process(data: dict):
result = await run_in_threadpool(sync_heavy_function, data)
return result
# NEVER: blocking call inside async def
import time, asyncio
@app.get("/bad")
async def bad():
time.sleep(5) # ❌ freezes the event loop - every request waits
return {}
@app.get("/good")
async def good():
await asyncio.sleep(5) # ✅ yields to other requests while waiting
return {}Watch out: A thread pool keeps the server free to answer other requests, but heavy number-crunching (image processing, big calculations) still fights for the one lock Python gives to running code. Move that work to a separate process or a task queue.