Async Programming in FastAPI: `async def` vs `def`
Async Programming in FastAPI: async def vs def
Introduction
One of FastAPI’s core strengths is its native support for asynchronous programming using Python’s async and await keywords. Understanding when and how to use async functions is key to unlocking the high performance that makes FastAPI so popular. This post clarifies the difference between async def and normal def endpoints.
What is Async?
Asynchronous code allows a program to pause a task that is waiting for something (like a network request to complete) and work on another task in the meantime. This is especially useful for web servers, which spend a lot of time waiting for I/O operations (e.g., reading from a database, calling another API, or reading a file).
How FastAPI Handles Endpoints
FastAPI is smart about how it runs your endpoint functions:
-
async deffunctions: If you declare your path operation function withasync def, FastAPI will run it directly on the main event loop. This means it can handle many concurrent requests efficiently, as long as you don’t use any “blocking” I/O calls. -
deffunctions: If you use a normaldeffunction, FastAPI will run it in an external thread pool andawaitit. This prevents a long-running, blocking function from freezing the main event loop.
When to Use async def
Use async def for I/O-bound tasks. This is when your code has to wait for an external resource. To get the benefits, you must use an async compatible library for the I/O operation.
Use async def when you need to await:
- Database queries (with an async driver like
asyncpgormotor). - Requests to external APIs (with a library like
httpx). - Reading/writing to files or the network (with
aiofiles).
Example: Calling an External API
Here, we use httpx to make an asynchronous call to an external API.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import asyncio
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/weather/{city}")
async def get_weather(city: str):
# This part doesn't block the server while waiting for the response
async with httpx.AsyncClient() as client:
# Simulate a slow API call
await asyncio.sleep(1)
# In a real app, you'd call an external service
# response = await client.get(f"https://api.weather.com/{city}")
# return response.json()
return {"city": city, "temperature": "25°C"}
While this endpoint is waiting for asyncio.sleep() or the httpx request, the server can process other requests.
When to Use def
Use a normal def for CPU-bound tasks. These are operations that perform heavy computations. Running them in a separate thread prevents them from blocking the event loop, which would make your entire application unresponsive.
Use def for:
- Processing a large file in memory.
- Performing complex mathematical or data science calculations.
- Any operation that uses a library that makes blocking I/O calls (like the standard
requestslibrary).
Example: A CPU-Bound Task
This endpoint simulates a CPU-intensive calculation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
from fastapi import FastAPI
app = FastAPI()
def complex_calculation(data: dict):
# Simulate a CPU-intensive task
time.sleep(2)
return {"result": sum(data.values())}
@app.post("/calculate/")
def calculate(data: dict):
# FastAPI runs this in a thread pool, so it doesn't block
result = complex_calculation(data)
return result
Even though time.sleep() is blocking, FastAPI is smart enough to run this function in a separate thread, so other requests can still be handled concurrently.
Conclusion
FastAPI gives you the flexibility to choose the right tool for the job.
- Use
async deffor I/O-bound operations withawaitto achieve high concurrency. - Use
deffor CPU-bound or blocking I/O operations to keep the main event loop free.
By understanding this distinction, you can build highly efficient and scalable APIs that make the most of modern hardware and asynchronous capabilities.
Suggested Reading