Comparing Real-Time Protocols
When designing a real-time feature, you have three primary architectural choices for pushing data to the client. Choosing the wrong one can lead to massive network overhead or battery drain.
Short & Long Polling
Short Polling: The client asks the server every X seconds if there is new data. This is simple but highly inefficient. Most requests return empty, wasting server CPU and mobile battery.
Long Polling: The client makes a request, but the server holds the connection open until new data is available or a timeout occurs. Once the client gets data, it immediately opens a new long-polling request. It works over standard HTTP, bypassing corporate firewalls easily, but it still incurs the overhead of HTTP headers on every new request (keep-alive usually reuses the underlying TCP connection).
Server-Sent Events (SSE)
SSE is a unidirectional protocol over HTTP. The client establishes a connection, and the server continuously streams data down to the client. The client cannot send data back over this same channel (it must make separate POST requests). The browser's EventSource reconnects automatically and sends Last-Event-ID so the server can resume; over HTTP/1.1 each stream uses one of the browser's roughly six connections per origin, a limit HTTP/2 multiplexing removes.
Best for: Real-time feeds, stock tickers, or live sports scores where data flows only from Server to Client.
WebSockets
WebSockets upgrade a standard HTTP connection into a fully bidirectional, persistent TCP connection. Both client and server can send data asynchronously with minimal overhead (no heavy HTTP headers).
Best for: Multiplayer games, collaborative editing (Google Docs), and low-latency chat applications.
| Protocol | Direction | Use Case |
|---|---|---|
| Long Polling | Emulated server-to-client push; client-to-server uses ordinary requests | Legacy support, strict firewalls |
| SSE | Server to Client only | Live feeds, notifications |
| WebSockets | Bi-directional | Chat, gaming, real-time collab |
Worked example: one million clients, one update per minute
Suppose 1,000,000 connected clients, each receiving on average one update every 60 seconds.
- Short polling every 5 s: 200,000 requests per second. Only about 1 in 12 polls returns data, so roughly 92 percent of the work is wasted. At around 800 bytes of request plus response headers, that is about 160 MB/s (1.3 Gbit/s) of header traffic alone.
- Long polling with a 30 s timeout: each client re-issues a request after every update or timeout. With updates arriving randomly every 60 s on average, the expected wait per request is about 60 x (1 - e^-0.5), roughly 23.6 s, so about 42,000 requests per second. Much better, but each response still pays full HTTP headers and a reconnect.
- SSE or WebSocket: 1,000,000 idle connections held open and about 16,700 messages per second pushed, each with only a few bytes of framing (WebSocket frame headers are 2 to 14 bytes). The cost moves from CPU and bandwidth to memory and connection management.
Detailed comparison
| Property | Long polling | SSE | WebSocket |
|---|---|---|---|
| Direction | Server to client push emulated; client sends via normal requests | Server to client only | Full duplex |
| Transport | Plain HTTP request per message batch | One long HTTP response (text/event-stream) | HTTP upgrade to the WebSocket protocol |
| Reconnect and resume | Manual | Built into EventSource with Last-Event-ID | Manual; you design session resume |
| Payload | Anything HTTP carries | UTF-8 text only | Text or binary frames |
| Proxy and CDN friendliness | Excellent | Good; buffering proxies must be configured not to buffer | Needs upgrade support and long idle timeouts |
| Typical use | Fallback, very constrained networks | Notifications, dashboards, LLM token streaming | Chat, multiplayer games, collaborative editing |
How real systems choose
- LLM APIs such as OpenAI's and Anthropic's stream tokens to clients using SSE, because the flow is one-way and HTTP semantics (auth headers, retries, proxies) come for free.
- Slack and Discord use WebSockets because clients send typing indicators, presence and acknowledgements continuously.
- Libraries such as Socket.IO historically started with long polling and upgraded to WebSocket when the network allowed it, which is why long polling survives as a fallback.
Failure modes
- A corporate proxy that buffers responses turns SSE into "nothing, then everything at once"; send periodic comment lines and disable buffering (for nginx, the X-Accel-Buffering header).
- Long polling can deliver duplicates when a response is lost after the server marked it sent; carry a cursor so the client asks for "after position N".