FastAPI is a great, high performance web framework but far from perfect. This document is intended to provide some tips and ideas to get the most out of it
All tested on the same sized Docker containers (2 CPU cores). The performance numbers below represent real CI-verified measurements.
Each optimization below has a measurable, compounding effect. When applied together, the total improvement is multiplicative, not additive.
| Optimization | Impact | Details |
|---|---|---|
| Gunicorn (1 worker) → FastAPI CLI (2 workers) | +50-65% throughput | Server runners |
| JSON → ORJSON response class | +4-13% throughput | JSON response classes |
| BaseHTTPMiddleware → Starlette ASGI | +35-44% throughput (avoiding BaseHTTPMiddleware cost) | Middleware |
| Best vs Worst combo | +100% throughput | Measured below |
| Optimization | Impact | Details |
|---|---|---|
| Sync → Async endpoints | +20-33% throughput | Sync vs Async |
| Gunicorn (1 worker) → FastAPI CLI (2 workers) | +50-65% throughput | Server runners |
| JSON → ORJSON response class | +4-13% throughput | JSON response classes |
| BaseHTTPMiddleware → Starlette ASGI | +35-44% throughput (avoiding BaseHTTPMiddleware cost) | Middleware |
| Best vs Worst combo | +297% throughput | Measured below |
CI run 30018301964 — all 4 jobs passed.
Using the big JSON response endpoint (1MB payload) across all tested combinations:
Configuration key:
w{N}= N worker processes,t{N}= N threads per worker.
- Gunicorn w1t0: Gunicorn with 1 worker process, 0 threads (single-process baseline)
- FastAPI CLI w2: FastAPI CLI (
fastapi run --workers 2) with 2 worker processes- ORJSON: ORJSONResponse (fast JSON serialization via orjson)
- Starlette ASGI: Starlette native ASGI middleware (not BaseHTTPMiddleware)
/sync/big_json_response)| Configuration | RPS | Latency |
|---|---|---|
| Best: FastAPI CLI (2 workers) + async + ORJSON + Starlette ASGI | 3405 | 29.4 ms |
| Worst: Gunicorn (1 worker, 0 threads) + sync + JSON + BaseHTTPMiddleware | 1695 | 59.1 ms |
| Improvement | +100.85% | -29.7 ms |
/async/big_json_response)| Configuration | RPS | Latency |
|---|---|---|
| Best: FastAPI CLI (2 workers) + async + ORJSON + Starlette ASGI | 3433 | 29.4 ms |
| Worst: Gunicorn (1 worker, 0 threads) + sync + JSON + BaseHTTPMiddleware | 864 | 180.1 ms |
| Improvement | +297% | -150.7 ms |
| Endpoint | Best RPS | Worst RPS | Improvement |
|---|---|---|---|
/sync/items/ |
2081 | 974 | +113.6% |
/async/items/ |
2656 | 867 | +206.4% |
When you combine ALL optimizations:
| Factor | Worst config | Best config |
|---|---|---|
| Server runner | Gunicorn (1 worker, 0 threads) | FastAPI CLI (2 workers) |
| Endpoint type | Sync | Async |
| Response class | JSON | ORJSON |
| Middleware | BaseHTTPMiddleware | Starlette ASGI (or none) |
| Nginx transport | TCP port | Unix socket |
| Combined RPS (big JSON) | 864 | 3433 |
| Combined improvement | ~4x throughput |
The difference between a default FastAPI setup and a properly optimized one is 4x throughput on big JSON responses and 3x on small payloads. These are not theoretical numbers — they are measured in CI on identical Docker infrastructure (2 CPU cores per container).
These patterns apply to any Python web application. They contribute to a robust and reliable application but are not FastAPI performance optimizations.
docker-compose build
app:
container_name: fastapi-performance-optimization
build:
context: app_files
dockerfile: Dockerfile
image: fastapi-performance-optimization:latest
cpus: 2
restart: always
ab -q -c 100 -n 1000 -T 'application/json' ...git clone git@github.com:KissPeter/fastapi-performance-optimization.git
pip3 install -r test_files/requirements.txt
pytest -vv -rP test_files/