What's the difference between WSGI and ASGI, and why does it matter?

7 minadvancedwebwsgiasgidjangofastapi

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

FrameworkInterfaceNotes
Flask (classic)WSGISynchronous by design; can run under Gunicorn
Django (traditional views)WSGIAsync views supported since Django 3.1, running under ASGI
FastAPIASGIBuilt async-first, typically served by Uvicorn/Hypercorn
StarletteASGIThe 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.