Backend

FastAPI vs Flask: Why Async Matters for Your API Throughput

May 2026 · 8 min read

Verdict up front: FastAPI for new Python APIs. The async support, Pydantic validation, and auto-generated OpenAPI docs make it the default choice in 2026. Stick with Flask if you have an existing app that works fine, or if your team isn't comfortable with async/type hints yet. Migration is doable in 2-3 weeks for a typical 20-endpoint API.

The async difference

Flask is WSGI — one worker handles one request at a time. With Gunicorn + 4 workers, you can handle 4 concurrent requests. Each request blocks the worker until it completes.

FastAPI is ASGI — async workers can handle thousands of concurrent requests by suspending on I/O. With Uvicorn + 4 workers, you can comfortably handle 1,000+ concurrent connections.

This matters for I/O-bound APIs (database queries, external API calls). It doesn't matter for CPU-bound work (image processing, ML inference).

Benchmark: a realistic API endpoint

Endpoint: receives a request, queries Postgres (5ms), calls a third-party API (200ms), returns JSON. Test: 100 concurrent users for 60 seconds.

  • Flask + gunicorn (4 workers, sync): 1,200 req/min, p95 latency 3.2s (queue buildup)
  • Flask + gunicorn + gevent (4 workers, eventlet): 18,000 req/min, p95 latency 280ms
  • FastAPI + uvicorn (4 workers, async): 28,000 req/min, p95 latency 220ms

Flask with gevent gets close to FastAPI on raw throughput. The gap is bigger when you factor in everything else FastAPI gives you.

Pydantic validation

FastAPI uses Pydantic for request/response models:

from pydantic import BaseModel
from fastapi import FastAPI

class CreateUser(BaseModel):
    email: str
    age: int
    is_active: bool = True

app = FastAPI()

@app.post("/users")
async def create_user(user: CreateUser):
    return {"id": 1, **user.dict()}

You get for free: type checking, default values, error responses (HTTP 422 with field-level errors), OpenAPI schema generation.

Flask equivalent (with marshmallow or manual validation): ~30 lines of boilerplate per endpoint. Across a 20-endpoint API, FastAPI saves 500+ lines of validation code.

Auto-generated OpenAPI docs

FastAPI generates Swagger UI (/docs) and ReDoc (/redoc) automatically from your type hints. No extra code. Frontend devs and API consumers can hit /docs and see every endpoint with example requests.

Flask needs flask-openapi3 or apispec + manual schema definitions. Doable but tedious to keep in sync.

Dependency injection

FastAPI's Depends() is genuinely useful for auth, DB sessions, shared config:

def get_current_user(token: str = Header()):
    return decode_jwt(token)

@app.get("/me")
async def me(user: User = Depends(get_current_user)):
    return user

Flask has decorators (@login_required) which work but are less composable. Once you have 5+ dependencies (auth, db, cache, feature flag, rate limit), FastAPI's DI scales better.

Type hints and IDE support

FastAPI requires type hints. Your IDE knows what every function returns, autocompletes everything, and Pyright/mypy catch bugs at write time.

Flask works fine without type hints. You can add them, but the framework doesn't reward you for it. Many Flask apps end up with partial type coverage that breaks more than it helps.

Ecosystem maturity

Flask has been around since 2010 — every Python library has Flask integration. SQLAlchemy, Celery, Flask-Login, Flask-Migrate, Flask-Admin — turnkey solutions for everything.

FastAPI is newer (2018). Most libraries work fine via Starlette (FastAPI's underlying ASGI framework). But you'll occasionally hit a library that's sync-only (e.g. some older AWS SDKs) and need to wrap it in run_in_executor.

Side-by-side

FeatureFastAPIFlask
Async supportNative (ASGI)Limited (sync underneath)
Request validationPydantic built-inmarshmallow / Pydantic add-on
OpenAPI / SwaggerAuto-generatedflask-openapi3 (manual)
Type hintsRequired (powers everything)Optional
Dependency injectionBuilt-in (Depends)Decorators
Throughput (I/O-bound)2-5x Flask syncBaseline (gevent closes gap)
Throughput (CPU-bound)SameSame
Cold start time~150ms~100ms
Boilerplate per endpoint5-10 lines15-30 lines
Ecosystem age2018+2010+
Learning curveMedium (async, type hints)Low
WebSocket supportBuilt-inflask-socketio add-on
Streaming responsesNative async generatorsGenerator-based, sync

When to choose FastAPI

  • New Python API project
  • I/O-heavy workload (lots of DB queries, external API calls)
  • You want automatic OpenAPI docs without effort
  • Team is comfortable with type hints and async/await
  • You'll have 20+ endpoints (validation boilerplate adds up)

When to stay with Flask

  • Existing Flask app that works fine
  • Heavy reliance on Flask-specific libraries (Flask-Admin, Flask-Login)
  • Team unfamiliar with async (forcing them to learn it slows you down)
  • Simple internal tools or webhooks (5-10 endpoints, no scale concerns)

Migration: Flask → FastAPI

For a 20-endpoint API: 2-3 weeks. Steps:

  1. Week 1: Set up FastAPI alongside Flask. Migrate auth, DB session management, error handling middleware.
  2. Week 2: Convert endpoints. Add Pydantic models. Convert sync DB calls to async (asyncpg or SQLAlchemy 2.0 async).
  3. Week 3: Rewrite tests (pytest works for both, but async fixtures differ). Update CI. Switch over via traffic split or DNS cutover.

Biggest gotcha: any sync library you depended on (Celery tasks, blocking SDK calls) needs careful wrapping. Don't mix sync and async sloppily — that's how event loop blocks happen.

Need to migrate Flask to FastAPI?

We've migrated 6 Python APIs from Flask to FastAPI. Typical 20-endpoint API takes 2-3 weeks including new type-hint coverage and test rewriting.

Book a discovery call

Related Posts

API Integration CostAI API Cost OptimizationCustom Slack Bot Cost
← All blog posts

Picking a Python framework for your next API?

Free 20-min call: we map your throughput needs, team's async experience, and existing dependencies to pick Flask, FastAPI, or Django REST.

Book a discovery call