Long Polling vs SSE vs WebSockets: Choose the Right Real-Time Protocol
Compare long polling, SSE, and WebSockets for real-time web.
20+ years shipping large-scale distributed systems. Everything here is grounded in real deployments.
Use WebSockets for low-latency bidirectional communication (chat, gaming). Use SSE for server-to-client streaming (notifications, live feeds) when you don't need client-to-server push. Use long polling only when you can't upgrade to SSE or WebSockets due to legacy infrastructure or restrictive proxies.
Imagine you're waiting for a package. Long polling is like calling the courier every 30 seconds to ask 'Is it here yet?' — you hang up and call again. SSE is like the courier giving you a tracking number that updates automatically on your phone. WebSockets is like a two-way radio — you can ask questions and get answers instantly.
Most real-time systems fail not because the protocol is slow, but because the wrong protocol was chosen. I've seen a chat app built on long polling bring a server farm to its knees at 2 PM on Black Friday. The team assumed 'real-time' meant WebSockets, but their ops team blocked WebSocket upgrades at the load balancer. They ended up with a polling loop that looked like a DDoS against themselves.
The problem is simple: you need the server to push data to the client without the client asking. The browser's HTTP request-response model doesn't support that natively. So we hacked around it. Long polling was the first hack. Then came SSE and WebSockets. Each trades off latency, complexity, and resource usage.
By the end of this, you'll be able to pick the right protocol for your use case, spot the failure modes before they hit production, and defend your choice in a system design interview. No fluff. Just what works and what doesn't.
Long Polling: The Hack That Won't Die
Long polling exists because HTTP 1.0 had no push mechanism. The client sends a request, the server holds it open until it has data (or a timeout), then responds. The client immediately sends another request. It's not real push — it's a loop that looks like push.
The problem? Every poll is a full HTTP request with headers, cookies, and TLS handshake overhead. At scale, that overhead kills you. I've seen a 50-node cluster reduced to 10% throughput because every client polled every 5 seconds. The network stack was drowning in SYN packets.
When does long polling make sense? When you can't upgrade the server or the client. Think embedded devices with minimal HTTP libraries, or legacy systems where you can't install WebSocket support. Otherwise, don't touch it.
SSE: The Underrated Workhorse
Server-Sent Events (SSE) is a standard that lets the server push text events over a single long-lived HTTP connection. The client uses EventSource API. It's unidirectional — server to client only. That's fine for live feeds, stock tickers, notifications, and log streaming.
Why isn't SSE more popular? Because WebSockets got all the hype. But SSE has advantages: it works over plain HTTP/1.1 (no upgrade handshake), automatically reconnects with last-event-id, and is simpler to implement. The downside: browser limits on concurrent connections (typically 6 per domain) and no binary data without base64 encoding.
I've used SSE to replace a WebSocket-based notification system that kept dropping connections under load. The EventSource auto-reconnect saved us from writing reconnection logic. The 6-connection limit? We sharded across subdomains.
WebSockets: Full-Duplex, But at a Cost
WebSockets provide a persistent, full-duplex connection over a single TCP socket. The client and server can send messages anytime. It's the go-to for low-latency bidirectional communication: chat, gaming, collaborative editing, financial tick data.
The cost? Complexity. You need a WebSocket library on both sides. You need to handle reconnection, heartbeats, and message framing yourself. WebSocket connections don't automatically reconnect like SSE. And they don't work through all proxies — many corporate firewalls strip the Upgrade header.
I once debugged a WebSocket outage where the load balancer's idle timeout was 60 seconds, but the WebSocket heartbeat was every 120 seconds. Connections were silently killed. The fix: set the heartbeat interval to 30 seconds and configure the load balancer to not timeout WebSocket connections.
When to Use What: A Decision Framework
Here's the rule of thumb I use:
- Need bidirectional, low-latency, and can control the network? WebSockets.
- Need server-to-client streaming only, and want simplicity? SSE.
- Can't upgrade the server or client, or need to traverse restrictive proxies? Long polling.
- Need binary data? WebSockets (or SSE with base64, but that's wasteful).
- Need to support millions of concurrent connections? SSE or WebSockets with async I/O. Long polling won't scale.
The browser's EventSource API limits you to 6 concurrent connections per domain. If you need more, use subdomains or switch to WebSockets. Also, SSE doesn't support custom headers — you can't send authentication tokens in the initial request. Workaround: use query parameters or cookies.
Production Gotchas: What Will Burn You
- Proxy timeouts: Load balancers, reverse proxies, and CDNs have idle timeouts. If your SSE or WebSocket connection sends no data for longer than the timeout, the proxy kills it. Fix: send keepalive pings (SSE: comment line; WebSocket: ping/pong).
- Connection limits: Browsers limit concurrent connections per domain (usually 6). If you open multiple SSE streams, you'll hit the limit. Fix: use subdomains or switch to WebSockets (which have a different limit, typically 255 per domain).
- Reconnection storms: When a server restarts, all clients reconnect simultaneously. This can overwhelm the server. Fix: implement exponential backoff with jitter on the client side. For SSE, the EventSource API supports this natively with the 'reconnection time' field.
- Memory leaks: Each connection holds server resources. If clients disconnect without proper cleanup, you leak memory. Fix: implement connection cleanup on close events and use heartbeats to detect dead connections.
retry field in the event stream. For WebSockets, implement it client-side. Also, use a connection limiter on the server to reject excess connections gracefully.The 3 AM Thread Pool Exhaustion
- Long polling ties up server threads for the duration of the poll.
- Always use async I/O or dedicated connection handlers for real-time protocols.
netstat -an | grep :80 | wc -l. 2. Identify if the server is using synchronous I/O (thread-per-request). 3. Migrate to async I/O (e.g., Node.js, Netty, or async servlets). 4. Consider switching to SSE or WebSockets to reduce connection overhead.curl -N http://localhost:3001/eventstcpdump -i any port 3001 -A | grep 'keepalive'res.write(': keepalive\n\n'); in a setInterval every 15 seconds.Key takeaways
Interview Questions on This Topic
How does SSE handle reconnection differently from WebSockets, and what are the implications for message ordering?
Frequently Asked Questions
20+ years shipping large-scale distributed systems. Everything here is grounded in real deployments.
That's Networking. Mark it forged?
3 min read · try the examples if you haven't