What's the difference between WSGI and ASGI, and why does it matter?
Quick Answer
**WSGI** (Web Server Gateway Interface) is the traditional synchronous standard interface between a Python web application and a web server — one request handled per worker thread/process at a time, no native async or WebSocket support. **ASGI** (Asynchronous Server Gateway Interface) is its async-capable successor, supporting `async`/`await`, WebSockets, and long-lived connections, letting a single worker handle many concurrent connections cooperatively via an event loop.
Detailed Answer
WSGI: the synchronous standard
def application(environ, start_response):
status = "200 OK"
headers = [("Content-Type", "text/plain")]
start_response(status, headers)
return [b"Hello, World!"]
A WSGI application is literally a callable matching this signature —
environ describes the incoming request, start_response sends back the
status/headers, and the return value is the response body. Every
production WSGI setup (Flask, Django's traditional mode, running under
Gunicorn/uWSGI) is built on this one synchronous, blocking-call contract:
one request occupies one worker (thread or process) until it's fully
handled.
Why WSGI's synchronous model limits concurrency
# A slow WSGI view blocks the entire worker handling it
def slow_view(request):
time.sleep(5) # this worker can't serve ANY other request meanwhile
return HttpResponse("done")
Scaling a WSGI application to handle more concurrent slow requests means adding more worker processes/threads (each with real memory overhead) — there's no way for a single WSGI worker to cooperatively juggle many in-flight requests the way an event loop can.
ASGI: the async-capable successor
async def application(scope, receive, send):
await send({
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain")],
})
await send({"type": "http.response.body", "body": b"Hello, World!"})
ASGI applications are async callables built around the same
scope/receive/send message-passing pattern used throughout asyncio
— a single worker process, running an event loop, can hold thousands of
concurrent connections open (including long-lived ones like WebSockets or
Server-Sent Events, which WSGI has no first-class way to represent at
all) as long as each one spends most of its time await-ing rather than
blocking.
Framework alignment
| Framework | Interface | Notes |
|---|---|---|
| Flask (classic) | WSGI | Synchronous by design; can run under Gunicorn |
| Django (traditional views) | WSGI | Async views supported since Django 3.1, running under ASGI |
| FastAPI | ASGI | Built async-first, typically served by Uvicorn/Hypercorn |
| Starlette | ASGI | The lightweight ASGI toolkit FastAPI itself is built on |
Why this distinction matters practically
Choosing WSGI vs ASGI isn't just a framework preference — it determines whether the application can efficiently support WebSockets, long-polling, or very high connection counts with modest resource usage. A traditional synchronous CRUD app with modest concurrency needs is often perfectly well served by WSGI (simpler mental model, mature tooling); an app needing real-time features or very high concurrent connection counts benefits substantially from ASGI's async model.
Interview-ready summary: WSGI is the synchronous, one-request-per-
worker standard interface web servers and Python apps have used for
decades; ASGI is its async successor, enabling a single worker to
cooperatively handle many concurrent (including long-lived, WebSocket)
connections via async/await. The choice determines whether the
application's concurrency model can scale via an event loop or only via
adding more OS-level workers.