Hello! Welcome back to our FastAPI course.
In our last lesson, you learned the fundamentals of asynchronous programming in Python. We covered how to define coroutines with async def, execute them with await, and run them concurrently using asyncio.gather. The key insight was that asyncio achieves high performance by switching between tasks during I/O-bound waiting periods.
Today, we'll apply that knowledge directly to FastAPI. This lesson bridges the gap between general async Python and building a real API. Our goal is to answer a crucial question you'll face with every endpoint you write.
Learning Outcome: Distinguish when to use async def vs. def for route handlers and explain how FastAPI handles blocking I/O.
This is a fundamental concept that sets FastAPI apart from Django. While Django is primarily synchronous (though it has added async support), FastAPI is built for async from the ground up. Understanding this distinction is the key to writing truly high-performance applications.
The Two Flavors of Route Handlers
In FastAPI, you can declare your path operation functions (the functions that handle requests for a specific URL) in two ways:
- As a normal synchronous function, using
def. - As an asynchronous function (a coroutine), using
async def.
from fastapi import FastAPI
import time
import asyncio
app = FastAPI()
@app.get("/sync-route")
def handle_sync_request():
# A standard, synchronous function
time.sleep(1) # Blocking operation
return {"message": "Hello from a sync route"}
@app.get("/async-route")
async def handle_async_request():
# An asynchronous function (coroutine)
await asyncio.sleep(1) # Non-blocking operation
return {"message": "Hello from an async route"}
Your choice between def and async def is not just stylistic; it tells FastAPI how to execute your code and has significant performance implications. Let's see a practical demonstration of how FastAPI handles these different function types.
How FastAPI Handles Requests Behind the Scenes
The video 'How FastAPI Handles Requests Behind the Scenes' from Code Collider provides a perfect, concise demonstration of how FastAPI executes async def functions with both blocking and non-blocking I/O, and how it handles def functions.
Watch from the beginning to 01:43. The video will show you three experiments: An async def route with a blocking time.sleep(). An async def route with a non-blocking asyncio.sleep(). A def route with a blocking time.sleep(). Observe the output when two requests are made to each endpoint in quick succession. Note which ones run sequentially, concurrently, or in parallel.
As you saw in the video, the behavior is quite different in each case. Let's break down why.
How FastAPI Handles async def
When you declare a route with async def, FastAPI runs it directly on the main event loop. This is the same event loop that is managing all incoming requests and outgoing responses.
- The Happy Path (Non-blocking I/O): If your function contains an
awaiton a non-blocking I/O operation (likeawait asyncio.sleep()or an async database query), it yields control back to the event loop. The event loop is then free to work on other things—like handling another request—while it waits. This is how a single FastAPI process can handle thousands of concurrent connections.

- The Pitfall (Blocking I/O): If you use a blocking I/O call (like
time.sleep()or the standardrequestslibrary) inside anasync deffunction, you block the entire event loop. No other requests can be processed until that blocking call is finished. This completely defeats the purpose of usingasyncand can cripple your server's performance.

How FastAPI Handles def
So what about normal def functions? Does a blocking call in a def route also freeze the server?
No. FastAPI is smart about this. When you declare a route with def, FastAPI runs it in a separate thread from an external thread pool.
This is a crucial feature. It means you can use legacy synchronous libraries or perform heavy CPU-bound computations without blocking the main event loop. The blocking operation happens in a separate thread, allowing the event loop to continue processing other requests.
This model is much closer to how a traditional WSGI server like Gunicorn handles requests for Django, where each request is typically isolated in its own thread or process. FastAPI provides this "safe" synchronous behavior while also offering the more performant asynchronous option.
The official FastAPI documentation has an excellent section explaining this mechanism.
Concurrency and async / await - FastAPI
Let's read the official explanation from the FastAPI documentation 'Concurrency and async / await'.
Navigate to the section 'Very Technical Details', and read the sub-section 'Path operation functions'. It explains exactly how FastAPI runs a def function in an external threadpool to prevent it from blocking the event loop.
The Decision Framework: When to Use Which?
Now we can establish a clear set of rules. The choice depends entirely on what your function does.
Use async def when:
- Your function is I/O-bound and you are using libraries that support asynchronous operations with
await. This is the primary use case.- Making HTTP requests with
httpx. - Querying a database with an async driver like
asyncpgormotor. - Reading/writing files with
aiofiles. - Communicating with WebSockets.
- Making HTTP requests with
- Your function needs to
awaitanother coroutine.
Use def when:
- Your function is CPU-bound. If your code is doing heavy, in-memory calculations (e.g., complex data analysis, image processing), running it in a separate thread prevents it from hogging the event loop's CPU time.
asyncprovides no benefit for CPU-bound work. - You are using a library that is synchronous-only. If you need to interact with a database using a blocking driver (
psycopg2) or make requests with therequestslibrary, you must wrap this logic in adefroute so FastAPI can run it safely in a thread.
The following resource provides a fantastic summary and further examples to solidify this framework.
Asynchronous vs. Synchronous Functions in FastAPI When to Pick ...
The article 'Asynchronous vs. Synchronous Functions in FastAPI' from Leapcell offers a clear, concise guide on this topic.
Read the section titled 'When to Use async def vs. def' for a clear, bulleted breakdown. Then, read the 'Conclusion' for a final summary of the key takeaways.
A Django Analogy
Let's connect this to your experience with Django and DRF:
- A
defroute in FastAPI is analogous to a standard Django or DRF view. It runs synchronously. In a production environment with a server like Gunicorn, each request is handled in a separate worker process/thread, so a blocking call in one view doesn't stop others. FastAPI's thread pool fordefroutes achieves a similar isolation. - An
async defroute in FastAPI is the new, high-performance paradigm. There isn't a perfect one-to-one analogy in the traditional Django world. While modern Django supportsasyncviews, it's not the default or core architecture as it is in FastAPI. Think of it as a specialized tool in Django, but the default, foundational engine in FastAPI. The responsibility of not blocking the event loop is the new tradeoff for the massive performance gain.
Conclusion
You have now learned one of the most important architectural concepts in FastAPI. Making the right choice between async def and def is critical for building robust and scalable applications.
Key Takeaways:
async def: Use for I/O-bound operations withawait-compatible libraries. This runs on the main event loop and enables massive concurrency. Never use blocking I/O calls inside anasync deffunction.def: Use for CPU-bound operations or when you must use a synchronous, blocking library. FastAPI protects the event loop by running these functions in a separate thread pool.- The Analogy: Think of
defas the familiar, safe Django-style request handling, andasync defas the supercharged, non-blocking mode that gives FastAPI its speed.
In our next lesson, we'll put all the pieces from this module together. You will define and run a minimal FastAPI application using Uvicorn, seeing firsthand how to create your first endpoints with the knowledge you've just gained.