asyncio framework with async/await syntax solves this problem by letting a single thread manage thousands of concurrent I/O operations, handing control to the event loop while one task waits so it can immediately start working on another. This is exactly how FastAPI achieves high throughput without requiring multiple threads or processes.
Concurrency vs. Parallelism
Before writing async code, it’s important to distinguish between two related but different concepts:
FastAPI is built for I/O-bound concurrency — your server spends most of its time waiting for database queries and external API calls, not crunching numbers.
Async & Await Declarations
Coroutines
Declaring a function withasync def creates a coroutine. Calling a coroutine does not run it — it returns a coroutine object. To actually execute it, you must await it:
asyncio.sleep() is the async equivalent of time.sleep(). Unlike time.sleep(), it releases control back to the event loop during the wait, allowing other tasks to run.
The Event Loop and Task Scheduling
The event loop is the engine that drives async programs:1
Run a task
The event loop starts executing a coroutine.
2
Hit an await
The coroutine reaches an
await expression (e.g., a database query or network call).3
Pause and switch
The loop pauses the current coroutine and picks up another ready task.
4
Resume
When the I/O operation completes, the loop resumes the original coroutine from where it paused.
Running Tasks Concurrently with asyncio.gather()
Without gathering, tasks run sequentially — the total time is the sum of all delays. With asyncio.gather(), tasks run concurrently — the total time is only as long as the slowest task:
gather, these three calls would take 2 + 1 + 3 = 6 seconds. With gather, they run concurrently and finish in just 3 seconds — the duration of the slowest call.
create_task() for More Control
When you need to start a coroutine and continue working before its result is ready, use asyncio.create_task():
Why FastAPI Uses Async
FastAPI is built on ASGI (Asynchronous Server Gateway Interface) and natively supportsasync def route handlers:
- FastAPI calls the coroutine.
- The coroutine hits
await asyncio.sleep(0.1)(or a real database query). - The event loop pauses this coroutine and immediately starts handling the next incoming request.
- When the database responds, the original coroutine resumes and sends its HTTP response.