Async vs sync route handlers

Difficulty: Intermediate

Question

When should you use async def vs def for FastAPI route handlers?

Answer

Use `async def` when the route performs I/O-bound operations that support async: async database drivers (asyncpg, motor, tortoise-orm), httpx async HTTP client, async file I/O, etc. This allows FastAPI to handle other requests while waiting.

Use `def` (sync) when the code uses blocking libraries (requests, psycopg2, standard file I/O). FastAPI automatically runs sync route handlers in a thread pool so they don't block the event loop.

The problem: Calling a blocking library inside `async def` blocks the event loop for all requests. Always use async alternatives in async routes.

Rule of thumb: If in doubt and you're using a blocking library, use `def`. FastAPI handles thread pool execution automatically.

Code examples

Async vs sync routes

import httpx
import requests
from fastapi import FastAPI

app = FastAPI()

# GOOD: async route with async HTTP client
@app.get("/async-fetch")
async def fetch_async():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/data")
    return response.json()

# GOOD: sync route with blocking library - runs in thread pool
@app.get("/sync-fetch")
def fetch_sync():
    response = requests.get("https://api.example.com/data")
    return response.json()

# BAD: blocking call inside async def - blocks event loop!
@app.get("/bad")
async def bad_route():
    response = requests.get("https://api.example.com/data")  # BLOCKS!
    return response.json()

Key points

Concepts covered

async/await, ASGI, concurrency, blocking I/O