FastAPI Async and Await

FastAPI supports both regular and asynchronous route functions. Understanding when to use async def versus def makes a real difference in how efficiently your API handles many simultaneous requests.

The Restaurant Kitchen Analogy

Synchronous chef (def):
  Takes order 1 → cooks → serves → takes order 2 → cooks → serves
  While cooking order 1, the chef stands idle waiting for the oven.
  Order 2 waits the entire time.

Asynchronous chef (async def):
  Takes order 1 → puts in oven → takes order 2 → puts in oven
  While both ovens are running, takes order 3.
  Serves each dish the moment it is ready.
  Same chef, far more customers served per hour.

The Core Concept: Waiting Without Blocking

Synchronous — blocks while waiting:
  result = database.query()   ← thread sits idle for 50ms waiting
  return result

Asynchronous — frees the thread while waiting:
  result = await database.query()   ← thread handles other requests
                                       during the 50ms wait
  return result

The await keyword says: "Start this operation. While it is waiting for the network or disk, go serve other requests. Come back here when the result is ready."

def vs async def in FastAPI

@app.get("/sync-route")
def sync_route():
    # Regular Python function
    # FastAPI runs this in a thread pool
    # Safe for blocking calls (like regular SQLAlchemy queries)
    return {"type": "synchronous"}

@app.get("/async-route")
async def async_route():
    # Async Python function
    # Runs on the event loop
    # MUST only use awaitable (async-compatible) operations inside
    return {"type": "asynchronous"}

When to Use async def

Use async def when your route calls async libraries:
  ✓ httpx (async HTTP client)
  ✓ asyncpg or databases (async database drivers)
  ✓ aiofiles (async file I/O)
  ✓ WebSocket operations
  ✓ Any function you call with await

Use regular def when using synchronous libraries:
  ✓ SQLAlchemy (synchronous ORM)
  ✓ requests library
  ✓ Standard file I/O
  ✓ CPU-heavy calculations

The Danger: Blocking Inside async def

# WRONG — blocks the event loop:
@app.get("/bad")
async def bad_route():
    time.sleep(5)    ← blocks ALL requests for 5 seconds
    return {}

# CORRECT — use asyncio.sleep for async waiting:
import asyncio

@app.get("/good")
async def good_route():
    await asyncio.sleep(5)   ← only pauses this route, others run freely
    return {}
# WRONG — calling blocking DB inside async def:
@app.get("/users")
async def get_users():
    users = db.query(User).all()   ← synchronous call blocks the event loop
    return users

# CORRECT — use regular def with synchronous SQLAlchemy:
@app.get("/users")
def get_users():
    users = db.query(User).all()   ← FastAPI runs this in a thread pool
    return users

Making Multiple Async Calls in Parallel

Use asyncio.gather() to run several async operations at the same time instead of one after another:

import asyncio
import httpx

@app.get("/dashboard")
async def dashboard():
    async with httpx.AsyncClient() as client:
        # Run both requests at the same time
        user_task    = client.get("https://api.example.com/users")
        product_task = client.get("https://api.example.com/products")

        user_res, product_res = await asyncio.gather(user_task, product_task)

    return {
        "users": user_res.json(),
        "products": product_res.json()
    }
Sequential (slow):
  fetch users    → wait 300ms → fetch products → wait 300ms = 600ms total

Parallel (fast):
  fetch users ──────────────────┐
  fetch products ───────────────┤  both finish at ~300ms = 300ms total
                                ▼
                         combine results

How FastAPI Handles Both Types

async def route → runs on event loop (single thread, non-blocking)
def route       → runs in thread pool (multiple threads, blocking allowed)

FastAPI manages both automatically.
You choose the right one based on your library.

Key Points

  • Use async def for routes that call async libraries (httpx, asyncpg, WebSockets).
  • Use regular def for routes that use synchronous libraries like SQLAlchemy.
  • Never call blocking operations (like time.sleep) inside async def — it freezes all requests.
  • Use asyncio.gather() to run multiple async operations in parallel and halve the wait time.
  • FastAPI automatically runs def routes in a thread pool so they do not block the event loop.

Leave a Comment

Your email address will not be published. Required fields are marked *