The registration system is now receiving many requests.
Workers are handling them, but another problem appears.
Many operations in backend systems involve waiting.
Examples:
• database queries
• external API calls
• file storage
• sending emails
These operations involve I/O (input/output).
Blocking I/O
If an operation is blocking, the worker waits.
Example timeline:
Worker receives Student A
↓
Calls email API
↓
Waits 2 seconds
↓
Returns response
During those two seconds, the worker cannot serve another request.
If 100 students register and each email takes 2 seconds, the last student waits 200 seconds (over 3 minutes).
That is unacceptable.
Async I/O
Async programming allows the worker to start an operation and continue doing other work while waiting.
Example:
Worker starts email request for Student A
↓
Email service responding...
↓
Worker processes Student B registration
Inside a single worker, the activity may look like this:
Worker 1
---------
Student A → waiting for email
Student B → validating request
Student C → saving to database
Student D → waiting for DB response
The worker is not idle. It switches between tasks whenever one is waiting for I/O.
How async works in FastAPI
When you define an endpoint with async:
@app.post("/register")
async def register(student: Student):
await save_to_database(student)
await send_email(student)
The await keyword tells the worker: "This operation will take time. Do something else while waiting."
Without await, the operation blocks.
Why a few workers can handle thousands of requests
Workers provide process-level concurrency.
Async programming provides I/O concurrency inside each worker.
Together they allow backend systems to handle many requests efficiently.
For example:
4 workers (process-level concurrency)
Each handling 250 async operations (I/O concurrency)
Total: 1000 concurrent requests
But high concurrency introduces another challenge.
When multiple workers modify the same data, things can break.
And that is where race conditions begin.