Python — FastAPI Development
FastAPI Performance Tuning Guide
Direct answer
Most slow FastAPI services are not slow because of FastAPI. In the audits I run, the causes rank: blocking sync calls inside async handlers, database problems like N+1 queries and undersized pools, and heavy serialization of large responses. My tuning order is measure per-endpoint p95s first, unblock the event loop, fix query patterns, switch to ORJSONResponse for large payloads, and only then touch worker counts.
FastAPI has a fast reputation, which makes it confusing when your service crawls — the framework is rarely the culprit. This is the diagnostic order I follow when a client hands me a slow FastAPI backend, ranked by how often each fix actually pays.
Key facts, with sources
- In the JetBrains Python Developers Survey 2024, which collected responses from more than 30,000 Python developers, FastAPI usage jumped from 29% to 38%, overtaking Django (35%) and Flask (34%) as the most-used Python web framework. (JetBrains Python Developers Survey 2024)
- The 2025 Stack Overflow Developer Survey shows FastAPI at 14.8% of respondents doing extensive work with it, edging out Flask at 14.4% and Django at 12.6%. (Stack Overflow Developer Survey 2025)
- FastAPI's official documentation cites independent TechEmpower benchmarks showing FastAPI applications running under Uvicorn as one of the fastest Python frameworks available, ranked only below Starlette and Uvicorn themselves. (FastAPI official documentation)
- FastAPI surpassed Flask in GitHub stars in December 2025, reaching roughly 88,000 stars compared to Flask's 68,400. (DZone)
- Industry analysis of FastAPI's 2025 growth reports about 40% year-over-year growth in job mentions and production adoption at companies including Uber, Netflix, and Microsoft. (byteiota)
Measure before you touch anything
Untargeted optimization is how teams spend a sprint making the fast parts faster. Before changing code, I get per-endpoint latency percentiles — p50, p95, p99 — from an APM tool or a simple timing middleware, plus separate timings for database and upstream API time within each request. Averages lie; the p95 is what your unlucky users feel, and it is usually dominated by one or two endpoints.
The decomposition matters as much as the totals. If an endpoint spends 900 of its 1000 milliseconds awaiting an upstream API, no amount of Python tuning helps — you need caching, parallelism, or a different upstream contract. Diagnosis first turns tuning from folklore into a checklist.
The blocking-call trap
The classic FastAPI performance bug: a sync call — requests instead of an async HTTP client, a sync database driver, a CPU-heavy PDF render — inside an async def handler. It does not just slow that request; it freezes the entire event loop, so every concurrent request on that worker stalls. The symptom is unrelated endpoints getting slow together under load, which sends teams hunting in exactly the wrong places.
The fixes, in order of preference: use genuinely async libraries; declare the endpoint with plain def so FastAPI runs it in the threadpool; or wrap the specific blocking call in asyncio.to_thread. Grep for known sync clients in async handlers during review — it is the highest-yield performance check I know.
import asyncio
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
app = FastAPI(default_response_class=ORJSONResponse)
@app.post("/reports")
async def build_report(req: ReportRequest):
# CPU-heavy or sync-only work must leave the event loop
pdf_bytes = await asyncio.to_thread(render_pdf, req.model_dump())
return {"size": len(pdf_bytes)}The database is the usual suspect
Once the loop is unblocked, most remaining latency lives in the database layer. N+1 query patterns top the list — an endpoint that fetches a list and then lazily loads a relationship per row, fixed with selectinload in one line. After that: missing indexes on columns you filter and sort by, unbounded list endpoints that need pagination, and connection pools sized so small that requests queue waiting for a connection under load.
Log slow queries and look at the actual SQL your ORM emits for your worst endpoint — teams are routinely surprised. A single well-placed index or one eager-load option often does more than every application-level tweak combined.
Serialization and response models
Serialization cost is invisible until responses get large, then it dominates. Pydantic validates every item when a response_model wraps a list of thousands of objects, and that CPU time runs per request. The cheap wins: return leaner payloads with dedicated list schemas instead of full detail objects, paginate anything unbounded, and switch to ORJSONResponse as the default response class — orjson serializes substantially faster than the standard library for typical API payloads.
Also question whether every response needs a response_model at all; on hot internal endpoints where the shape is already guaranteed by construction, skipping the extra validation pass is a legitimate trade once you understand what you are giving up.
Workers, keep-alive, and the deployment layer
Only after code-level fixes do I touch deployment settings, because worker tuning cannot fix a blocked event loop — it just multiplies it. For IO-bound services, a small number of Uvicorn workers per container scaled horizontally is the sane default; for mixed workloads, roughly matching workers to available cores is the usual starting point before load testing tells you otherwise.
Behind a load balancer, set Uvicorn's keep-alive timeout longer than the balancer's idle timeout to avoid connection-reset errors under sustained traffic. And put obvious caching where reads are hot and staleness is tolerable — a short-TTL Redis cache in front of an expensive aggregate endpoint routinely removes more p95 latency than any amount of micro-optimization.
When to hire senior help
Bring in senior help when your API needs to handle real concurrency, when you are designing service boundaries and auth for the first time, or when an existing FastAPI codebase mixes sync and async code and latency is degrading. An experienced engineer can usually diagnose event-loop blocking and connection-pool misconfiguration in days, which is far cheaper than re-architecting after launch. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Python — FastAPI Development projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Calling blocking libraries (classic SQLAlchemy sessions, requests, heavy file I/O) inside async def endpoints, which stalls the event loop and erases FastAPI's concurrency advantage
- ✕Deploying a single Uvicorn process with no process manager or worker scaling, leaving most CPU cores idle under production load
- ✕Treating the auto-generated OpenAPI docs as a versioning strategy, then breaking mobile and partner clients when response schemas change
- ✕Running large payloads through deeply nested Pydantic models on every request and response, adding serialization latency that shows up only at scale
Frequently asked questions
Why is my FastAPI app slow under concurrent load?
The most common cause is a blocking synchronous call — a sync HTTP client, sync database driver, or CPU-heavy function — inside an async def endpoint. It freezes the event loop, stalling every concurrent request on that worker, which is why unrelated endpoints slow down together. Fix it with async libraries, plain def endpoints that run in the threadpool, or asyncio.to_thread.
Does ORJSONResponse make FastAPI faster?
For endpoints returning large JSON payloads, yes — orjson serializes considerably faster than the standard library, and setting it as the default response class is a one-line change. It will not rescue an endpoint whose time goes to the database or an upstream API, so check your latency breakdown first; serialization typically only dominates on big list responses.
How many Uvicorn workers should I run per container?
For IO-bound APIs, start with one or two workers per container and scale horizontally by adding containers — the async event loop handles high concurrency per worker. For workloads with real CPU time per request, roughly matching workers to available cores is the conventional starting point. Load test your actual traffic shape before deviating; worker count cannot compensate for a blocked event loop.
Is FastAPI mature enough for production?
Yes. It was the most-used Python web framework in the JetBrains 2024 survey at 38%, and companies including Uber, Netflix, and Microsoft run it in production. The ecosystem for auth, ORMs, and testing is now well established.
How much faster is FastAPI than Flask or Django really?
Independent TechEmpower benchmarks place FastAPI among the fastest Python frameworks, and published comparisons show several times Flask's throughput on I/O-bound endpoints. For CPU-bound work or database-bottlenecked apps, the framework choice matters far less than query and infrastructure design.
Should we pick FastAPI or Django for a new SaaS backend?
FastAPI suits API-first products, microservices, and ML model serving because of async support and automatic OpenAPI docs. Django ships batteries included (admin, ORM, auth) and is often faster to launch a conventional CRUD product. Many teams run both, per the JetBrains finding that a third of Django developers also use Flask or FastAPI.
Bottom line: Dhairya Senjaliya ships Python — FastAPI Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.