WebSockets vs SSE vs Long Polling
HTTP was designed for the client to ask and the server to answer. Real-time features — notifications, live dashboards, chat, collaborative editing, streaming AI responses — need the server to send data when something happens. Three techniques dominate: long polling, which bends ordinary HTTP requests into a push channel; Server-Sent Events (SSE), a standard one-way stream from server to browser over HTTP; and WebSockets, a full-duplex connection that both sides can write to at any time.
Short verdict: use SSE when the server pushes and the client mostly listens — notifications, feeds, progress, token-by-token LLM output. Use WebSockets when both sides send frequent messages with low latency — chat with typing indicators, multiplayer games, collaborative editors. Use long polling only as a fallback where streaming connections are blocked or for very infrequent updates. Most applications that reach for WebSockets would be simpler with SSE plus ordinary HTTP requests for the client's actions.
TL;DR
- Long polling: the client sends a request; the server holds it until data arrives or a timeout, then the client asks again. Works everywhere; inefficient at scale.
- SSE: one long HTTP response streaming
text/event-streamevents; server → client only, automatic reconnection withLast-Event-ID, text only. - WebSockets: an upgraded TCP connection with two-way text and binary messages; you build reconnection, heartbeats, and resumption yourself.
- SSE works naturally with HTTP/2, existing auth cookies, proxies, and CDNs; WebSockets need upgrade-aware proxies and load balancers that tolerate long-lived connections. Sticky routing is required only for long-polling fallbacks such as Socket.IO's.
- All three need a fan-out layer (Redis pub/sub, a message broker, or a managed service) once you run more than one server.
- WebTransport over HTTP/3 is emerging for low-latency, multi-stream use cases but is not yet a universal default.
Quick Example
The same "new notification" feature with SSE — a Node.js endpoint and the browser's built-in EventSource:
And the WebSocket equivalent, where the client can also send messages on the same connection:
Core Concepts
Head-to-Head
Long Polling
The client asks for updates; the server holds the request open — often 20–30 seconds — and responds as soon as data arrives or the timeout expires, and the client immediately asks again. It works through any proxy and needs no special server support, but each message costs a full request, messages arriving between polls must be buffered, and ordering and gaps need a cursor such as "events after ID 4182".
Server-Sent Events
SSE is one HTTP response that never finishes. The server writes events as lines of text; the browser's EventSource parses them, dispatches named events, and reconnects automatically, sending the last received event ID so the server can replay what was missed. Because it is ordinary HTTP, it works with existing authentication cookies, compression, and HTTP/2 multiplexing. See Server-Sent Events.
WebSockets
A WebSocket starts as an HTTP request with an Upgrade header and becomes a persistent, full-duplex connection carrying framed messages in both directions. It is the right tool when the client sends frequent small messages and latency matters. In exchange, you implement reconnection, heartbeats, message acknowledgment, and resumption yourself — or use a library or managed service that does. See WebSockets.
Infrastructure and Scaling
Connections and Browser Limits
Over HTTP/1.1, browsers allow only about six concurrent connections per origin, and every open SSE stream uses one — users with many tabs can exhaust them. HTTP/2 and HTTP/3 multiplex streams over one connection, which removes the problem for SSE. WebSockets open their own connections regardless.
Load Balancers and Proxies
- SSE needs response buffering disabled (for example
X-Accel-Buffering: noorproxy_buffering offin Nginx) and idle timeouts longer than your heartbeat interval. - WebSockets need load balancers that support the upgrade and long-lived connections. An established connection stays on one server without any affinity; sticky sessions are required only when a long-polling fallback (as in Socket.IO) spreads one session across many requests. Some setups add them so a reconnecting client returns to a server holding its state, although resumable designs avoid depending on that.
- Deployments drop every open connection; clients must reconnect gracefully, preferably with jittered backoff so all clients do not return at once.
Fan-Out Across Servers
With more than one server, the server holding a user's connection may not be the one that produced the event. A pub/sub layer — Redis pub/sub or streams, a message broker, or a managed real-time service — distributes events to whichever server holds each connection. This is the same for all three techniques and is usually the hardest part of real-time architecture.
The Decision Framework
A useful rule: if the client's messages could be ordinary POST requests without anyone noticing the latency, use SSE for the downstream channel and HTTP for the upstream.
Best Practices
Design for Reconnection and Missed Messages
Every connection drops eventually — deployments, mobile networks, laptop sleep. Give events IDs, let clients resume from the last one they saw, and fall back to a full refresh when the gap is too large.
Send Heartbeats
Comments in SSE (: ping) or ping messages over WebSockets keep idle connections alive through proxies and detect dead connections quickly.
Authenticate the Connection and Authorize Each Channel
Check credentials when the connection opens and check permissions when a client subscribes to each channel or topic. Long-lived connections also need a plan for expired sessions and revoked access.
Bound Per-Connection Memory
Slow clients cause server-side buffers to grow. Cap queues per connection and disconnect clients that cannot keep up, rather than letting one slow consumer exhaust memory.
Common Mistakes
WebSockets for One-Way Updates
Proxy Buffering an Event Stream
Forgetting Multi-Server Fan-Out
FAQ
Is SSE better than WebSockets?
For server-to-client updates, often yes: it is simpler, reconnects automatically, works over plain HTTP with existing authentication, and multiplexes over HTTP/2. WebSockets are better when the client sends frequent messages and needs the lowest possible latency in both directions.
Is long polling still used?
Yes, as a fallback where streaming connections are blocked, and for infrequent updates where simplicity matters more than efficiency. Several real-time libraries use it as a transport of last resort.
Can SSE send binary data?
Not directly — events are UTF-8 text. Binary data can be base64-encoded, or fetched separately via a URL included in the event. WebSockets support binary frames natively.
Why do my SSE events arrive late or in bursts?
Usually because a proxy, load balancer, or compression middleware is buffering the response. Disable buffering for the stream endpoint and flush after each event.
What is WebTransport?
A newer browser API built on HTTP/3 that offers multiple streams and unreliable datagrams over one connection, useful for games and media. Support and tooling are still maturing, so WebSockets and SSE remain the common defaults.
Related Topics
- WebSockets — The full-duplex protocol in depth
- Server-Sent Events — Event streams, reconnection, and resumption
- HTTP — The protocol all three build on
- HTTP/3 and QUIC — The transport behind WebTransport
- Redis Pub/Sub and Streams — Fan-out across servers
- Load Balancing — Long-lived connections behind load balancers
- WebSocket Reconnect, Ordering, and Fan-Out — Making WebSockets reliable in production