
Can FastAPI Handle Millions of Orders? Yes, No, and It Depends
How FastAPI handles high traffic: ASGI vs WSGI, Starlette, Pydantic, async vs def, concurrency vs parallelism, and what really decides scale.
๐ Part 2 of the Ship It series. Missed Part 1? โFastAPI Is Fast.โ So Why Is Your API Slow?
Imagine a food-delivery app processing 2 million orders a day.
Sounds huge, right? Grab a calculator:
2,000,000 orders รท 86,400 seconds โ 23 orders per second
Twenty-three. On average, almost any framework can handle that.
So why do high-traffic systems still fall over? Two reasons:
Traffic isn't average. Dinner time can mean several times the average load, all at once.
Most of a request's life is spent waiting on the database, the payment gateway, the restaurant's system, the delivery partner API.
The real question isn't "how fast is my framework?" It's "what does my server do while it waits?"
That's where FastAPI's design gets interesting. Let's open it up. ๐ง
"Fast" means two different things
| Kind of fast | Where it comes from |
|---|---|
| Runtime fast | An async-capable stack that doesn't sit idle while waiting on I/O |
| Developer fast | Type hints, Pydantic validation, dependency injection, and auto-generated docs |
They're related, but don't mix them up. FastAPI doesn't make your Python code run faster. It wastes less time waiting, and it saves you from writing a lot of plumbing.
The thought experiment: an order service
Say the order-processing microservice for our food app is written in a classic synchronous style. For this workload, three things start to hurt:
Synchronous request handling by default: a request waiting on the DB holds up its worker
Validation is DIY: you bolt on your own checks for every payload
Docs are DIY: the frontend team keeps asking "what does this endpoint expect?"
FastAPI's answer to each:
โก Async path operations
โ Pydantic validation for requests and responses
๐ Automatic OpenAPI docs
To be fair, Flask is still a great framework for plenty of apps. This is about matching the tool to the workload, not crowning a winner.
What's actually inside FastAPI
FastAPI isn't one giant framework. It's a thin, opinionated layer on top of two solid libraries:
Your API code
โ
FastAPI โ conventions, DI, OpenAPI, developer experience
โ
Starlette โ the web engine: routing, middleware, WebSockets
โ
ASGI โ the contract between server and app
โ
Uvicorn โ the ASGI server
FastAPI
โโโ Pydantic โ validation and serialization
Starlette: the web engine
Starlette handles the low-level web work:
Routing:
/registergoes here,/logingoes thereMiddleware: auth checks, headers, CORS, compression, error handling
WebSockets: two-way, real-time communication
Background tasks: small jobs that run after the response is sent
Pydantic: the bouncer at the door ๐ช
Pydantic answers one question: does this data match what I expect?
from pydantic import BaseModel, EmailStr
class UserCreate(BaseModel):
email: EmailStr
password: str
Wrong type, missing field, or bad email? The request is rejected with a clear error before it reaches your logic. And the same model shows up in your API docs automatically.
๐ก
EmailStrneeds an extra package:pip install "pydantic[email]"
Opinionated vs unopinionated
| Unopinionated | Opinionated (FastAPI) |
|---|---|
| You decide everything | Sensible defaults are provided |
| More freedom, more decisions | Less boilerplate, more consistency |
| Teams invent their own conventions | The framework sets the conventions |
Opinionated doesn't mean "automatically correct". It means fewer decisions you have to repeat on every project.
WSGI vs ASGI: the heart of it
| WSGI | ASGI |
|---|---|
| The traditional synchronous interface | An async-capable interface |
| Built around a simple request โ response | Event-driven; supports WebSockets too |
| A blocked request occupies a worker | An async app can await and let other work proceed |
| Classic Flask deployments | FastAPI, Starlette |
Here's the picture that made it click for me:
WSGI-style
Worker A โ Request 1 โ waitโฆ โ done
Worker B โ Request 2 โ waitโฆ โ done
Worker C โ Request 3 โ waitโฆ โ done
ASGI-style
One event loop
โโโ Request 1 โ awaiting DB
โโโ Request 2 โ awaiting payment API
โโโ Request 3 โ processing
โโโ Request 4 โ awaiting network
The restaurant model ๐ฝ๏ธ
| Component | Restaurant role |
|---|---|
| ASGI server | The entrance that lets customers in |
| Starlette | The floor manager who routes customers |
| FastAPI | The house rules that keep everything organised |
| Pydantic | The order checker: "is this order complete?" |
| Your endpoint | The kitchen doing the actual cooking |
| DB / AI / external APIs | Suppliers the kitchen waits on |
The big insight: while one order waits for a supplier, the kitchen shouldn't stop cooking every other order.
Concurrency โ parallelism
| Concurrency | Parallelism |
|---|---|
| Many tasks make progress by taking turns while others wait | Many tasks run at the same time on multiple cores |
| Great for I/O-bound work (APIs, DBs) | Needed for CPU-bound work (heavy maths, ML inference) |
async / await |
Multiple processes |
Async doesn't make one worker do 1,000 things at once. It means the worker doesn't sit idle while waiting.
๐ง Quiz time
Both endpoints call an external API using the popular (synchronous) requests library. Which one is better under load?
import requests
from fastapi import FastAPI
app = FastAPI()
@app.get("/a")
async def endpoint_a():
r = requests.get("https://example.com/orders")
return r.json()
@app.get("/b")
def endpoint_b():
r = requests.get("https://example.com/orders")
return r.json()
Think about it before scrolling. ๐
.
.
.
Answer: B, the one without async. ๐ฎ
requests is blocking. Inside async def, it freezes the event loop and every other request waits. A plain def endpoint is run by FastAPI in a threadpool, so the blocking call doesn't stall the loop.
If you want async def, use an async client such as httpx.AsyncClient with await.
Rule of thumb
| Your code usesโฆ | Use |
|---|---|
Async libraries (await-able DB driver, HTTP client) |
async def |
| Blocking libraries you can't replace | def |
| Heavy CPU work | Separate processes, workers, or a task queue |
Did you get it right? Tell me in the comments. ๐
Free docs, zero effort ๐
Because FastAPI follows the OpenAPI standard, you get these out of the box:
/docs โ Swagger UI (try endpoints in the browser)
/redoc โ ReDoc (clean reference docs)
/openapi.json โ the machine-readable schema
No more Slack threads asking "what fields does this endpoint need?" The contract is generated from your code.
The full request journey
Client
โ
ASGI server (Uvicorn)
โ
Starlette: routing, middleware
โ
FastAPI: dependencies
โ
Pydantic: validation and parsing
โ
Your endpoint
โ
DB / external API / LLM โ mostly waiting here
โ
Response serialization
โ
Client
This is exactly why the model fits AI backends so well. An LLM call is a long wait, and an async server can serve other users in the meantime.
Soโฆ can FastAPI handle millions of orders?
Async is one tool, not the whole toolbox. At real scale you combine:
Async concurrency for I/O-bound waits
Multiple worker processes to use every CPU core:
uvicorn main:app --workers 4
Horizontal scaling: more instances behind a load balancer
Fast dependencies: indexes, connection pooling, caching, queues
| Layer | What can sink you |
|---|---|
| Framework | Routing, validation, serialization |
| ASGI server | Connection handling |
| Database | Missing indexes, pool exhaustion, locks |
| External services | Latency, rate limits, missing timeouts |
| Infrastructure | CPU, memory, autoscaling |
| Architecture | No caching, no queues, no observability |
So: yes, no, and it depends. FastAPI removes one important class of bottleneck. The rest of the system still has to keep up.
TL;DR
"Fast" means runtime efficiency and developer speed. They're different benefits.
FastAPI = Starlette (web engine) + Pydantic (validation) + OpenAPI (docs), on ASGI.
Async shines when your app spends time waiting.
Concurrency โ parallelism.
Blocking code inside
async defis worse than plaindef.Scale = async + workers + horizontal scaling + fast dependencies.
Keep digging
Your turn ๐ฌ
What's the sneakiest blocking call you've found hiding inside an async def? A DB driver, an SDK, a time.sleep someone forgot?
Drop it in the comments. The best ones might show up in a future post. ๐
โฌ ๏ธ Previous: โFastAPI Is Fast.โ So Why Is Your API Slow? โก๏ธ Next: coming soon. Follow the series so you don't miss it.

