FastAPI leverages Python's asyncio
library to achieve high performance, especially when dealing with operations that involve waiting, such as network requests or reading files. This capability is exposed directly through how you define your API route functions.
To create an asynchronous route handler in FastAPI, you define your function using the async def
syntax instead of the standard def
. This signals to FastAPI that the function might perform operations that can be paused (await
ed) without blocking the entire server process.
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/async-data")
async def get_async_data():
# Simulate an I/O-bound operation, like fetching data from a database
# or calling another API. asyncio.sleep is often used as a placeholder.
await asyncio.sleep(1) # Pause execution here for 1 second
return {"message": "Data fetched asynchronously!"}
@app.get("/sync-data")
def get_sync_data():
# Simulate a synchronous operation or a quick task.
# If this involved significant I/O without being async,
# it could block the server.
return {"message": "Data fetched synchronously!"}
In the example above, the get_async_data
function is an asynchronous route handler. The await asyncio.sleep(1)
line is crucial. When the execution reaches this point, instead of halting everything, the function signals to the underlying event loop, "I need to wait for something here; you can go do other work in the meantime."
await
and the Event LoopPython's async
/await
syntax works on top of an event loop. Think of the event loop as a manager that keeps track of multiple tasks. When an async
function encounters an await
expression (which must be used on another awaitable object, like the result of another async
function call or certain I/O operations), it effectively tells the event loop, "Pause me here until this await
ed operation completes. You can run other pending tasks."
This cooperative multitasking allows a single Python process (running the event loop) to handle many concurrent connections efficiently. While one route handler is await
ing a database response, the event loop can switch to handle an incoming request, process another route handler that just finished its await
, or manage other background activities.
Consider this sequence for the /async-data
endpoint:
/async-data
.get_async_data
function.await asyncio.sleep(1)
.get_async_data
function yields control back to the event loop.asyncio.sleep(1)
operation completes.get_async_data
function to run.await
line.{"message": "Data fetched asynchronously!"}
.During the 1-second await
, the server was not idle; it was free to process other work. This is fundamentally different from a traditional synchronous function using time.sleep(1)
, which would block the entire execution thread for that duration, preventing it from handling any other requests.
Diagram comparing the flow of an asynchronous route yielding control versus a synchronous route blocking execution.
The primary advantage of using async def
for your route handlers shines when the handler needs to perform I/O-bound operations. These are tasks where the program spends most of its time waiting for external resources, such as:
By using await
for these operations, your FastAPI application can handle a significantly larger number of concurrent requests with fewer server resources compared to a purely synchronous framework, because it doesn't waste time actively waiting.
FastAPI is clever. If you define a standard route handler using def
instead of async def
, like get_sync_data
in our first example, FastAPI understands that this function might contain blocking code. To prevent such synchronous code from blocking the main event loop, FastAPI runs it in a separate thread pool. This allows the event loop to remain responsive while the synchronous function executes in its own thread.
While this provides compatibility and prevents simple synchronous code from halting your entire application, it's important to understand the trade-offs. Managing thread pools introduces its own overhead, and it's generally more efficient to use async def
with await
for genuine I/O-bound work. CPU-bound tasks, like complex computations or model inference (which we'll discuss next), present a different challenge even for the thread pool approach.
Understanding how async
and await
allow your route handlers to pause and resume is fundamental to building high-performance APIs with FastAPI, especially when integrating tasks like data fetching or preprocessing steps that involve waiting for external systems.
© 2025 ApX Machine Learning