Senior 3 min · June 25, 2026

Long Polling vs SSE vs WebSockets: Choose the Right Real-Time Protocol

Compare long polling, SSE, and WebSockets for real-time web.

N
Naren Founder & Principal Engineer

20+ years shipping large-scale distributed systems. Everything here is grounded in real deployments.

Follow
Production
production tested
June 25, 2026
last updated
1,663
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

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.

✦ Definition~90s read
What is Long Polling vs SSE vs WebSockets?

Long polling, Server-Sent Events (SSE), and WebSockets are three techniques for pushing real-time data from server to client. Long polling emulates push by keeping HTTP requests open. SSE is a unidirectional standard over HTTP. WebSockets provide full-duplex persistent connections.

Imagine you're waiting for a package.
Plain-English First

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.

longPollServer.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// io.thecodeforge — System Design tutorial
// Long polling server using Node.js (naive, for illustration)
const http = require('http');

const clients = []; // WARNING: in production, use a proper queue

http.createServer((req, res) => {
  if (req.url === '/poll') {
    // Hold the response open until we have data or timeout
    clients.push(res);
    req.on('close', () => {
      // Clean up if client disconnects early
      const idx = clients.indexOf(res);
      if (idx !== -1) clients.splice(idx, 1);
    });
    // Timeout after 30 seconds to prevent resource leak
    setTimeout(() => {
      if (!res.writableEnded) {
        res.end('[]'); // empty response
      }
    }, 30000);
  } else if (req.url === '/push') {
    // Simulate pushing data to all polling clients
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      clients.forEach(client => {
        client.end(body);
      });
      clients.length = 0;
      res.end('ok');
    });
  }
}).listen(3000);
console.log('Long poll server on :3000');
Output
Server starts. Clients hit /poll and block. When /push is called, all waiting clients receive the data and reconnect.
Production Trap: Thread Pool Exhaustion
Every long polling request ties up a thread for the poll duration. In a synchronous server (e.g., Tomcat with thread-per-request), 1000 clients polling every 30 seconds means 1000 threads blocked. Your thread pool will exhaust, and other requests (like payments) will be rejected. Always use async I/O or a dedicated event loop for long polling.
Real-Time Protocol Comparison: Long Polling, SSE, WebSockets THECODEFORGE.IO Real-Time Protocol Comparison: Long Polling, SSE, WebSockets Decision framework for choosing the right real-time communication protocol Long Polling Client holds request open until server responds Server-Sent Events (SSE) Unidirectional server push over HTTP WebSockets Full-duplex persistent TCP connection Decision Framework Match protocol to use case: latency, direction, overhead ⚠ Long polling wastes server resources under high concurrency Use SSE for one-way push or WebSockets for bidirectional needs THECODEFORGE.IO
thecodeforge.io
Real-Time Protocol Comparison: Long Polling, SSE, WebSockets
Long Polling Sse Websockets
Long Polling Request LifecycleTHECODEFORGE.IOLong Polling Request LifecycleClient sends request, server holds, responds, repeatsClient sends reqHTTP request to serverServer holds openWaits for data or timeoutServer respondsSends data or empty responseClient reconnectsImmediately sends new request⚠ Not real push — it's a polling loop disguised as pushTHECODEFORGE.IO
thecodeforge.io
Long Polling Request Lifecycle
Long Polling Sse Websockets

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.

sseServer.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// io.thecodeforge — System Design tutorial
// SSE server using Node.js
const http = require('http');

http.createServer((req, res) => {
  if (req.url === '/events') {
    // Set SSE headers
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
      'Access-Control-Allow-Origin': '*'
    });

    // Send a keepalive comment every 15 seconds to prevent proxy timeout
    const keepalive = setInterval(() => {
      res.write(': keepalive\n\n');
    }, 15000);

    // Simulate sending events
    let eventId = 0;
    const sendEvent = setInterval(() => {
      const data = JSON.stringify({ id: ++eventId, message: 'Hello ' + eventId });
      res.write(`id: ${eventId}\nevent: message\ndata: ${data}\n\n`);
    }, 2000);

    req.on('close', () => {
      clearInterval(keepalive);
      clearInterval(sendEvent);
    });
  }
}).listen(3001);
console.log('SSE server on :3001');
Output
Server starts. Client connects to /events and receives a new event every 2 seconds. Keepalive comments every 15 seconds prevent proxy timeouts.
Senior Shortcut: Auto-Reconnect with last-event-id
The EventSource API automatically reconnects on connection drop. It sends a Last-Event-ID header with the last received event id. Your server must handle this: if the header is present, replay events from that id onward. This gives you reliable delivery with zero client code.

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.

wsServer.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// io.thecodeforge — System Design tutorial
// WebSocket server using Node.js (ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3002 });

wss.on('connection', (ws, req) => {
  console.log('Client connected from', req.socket.remoteAddress);

  // Heartbeat to detect dead connections
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });

  // Handle incoming messages
  ws.on('message', (message) => {
    // Echo back for simplicity
    ws.send(`Echo: ${message}`);
  });

  // Send a welcome message
  ws.send(JSON.stringify({ type: 'welcome', message: 'Connected' }));
});

// Heartbeat interval: every 30 seconds
const heartbeat = setInterval(() => {
  wss.clients.forEach((ws) => {
    if (ws.isAlive === false) {
      return ws.terminate();
    }
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

wss.on('close', () => clearInterval(heartbeat));
console.log('WebSocket server on :3002');
Output
Server starts. Clients connect via ws://localhost:3002. Server echoes messages and sends a welcome. Heartbeat pings every 30 seconds; dead connections are terminated.
Never Do This: Forget Heartbeats
Without heartbeats, TCP connections can remain half-open indefinitely. The server thinks the client is connected, but the client is gone. You'll leak file descriptors and memory. Always implement ping/pong or a custom heartbeat. Set the interval to less than half the load balancer's idle timeout.

When to Use What: A Decision Framework

  • 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.

Real-Time Protocol Decision Tree
IfBidirectional communication needed?
UseYes → WebSockets. No → SSE or long polling.
IfCan you upgrade server and client to support WebSockets?
UseYes → WebSockets. No → SSE (if unidirectional) or long polling.
IfNeed to traverse corporate proxies that block WebSocket upgrades?
UseYes → SSE or long polling. No → WebSockets.
IfConcurrent connections > 6 per domain?
UseYes → WebSockets or SSE with subdomains. No → SSE is fine.
IfNeed binary data?
UseYes → WebSockets. No → SSE is simpler.
SSE vs WebSockets TradeoffsTHECODEFORGE.IOSSE vs WebSockets TradeoffsSimplicity vs full-duplex capabilitySSEUnidirectional: server to client onlyUses standard HTTP, simple EventSource APIAutomatic reconnection built inText-only, no binary support nativelyWebSocketsFull-duplex: both sides send anytimeRequires custom handshake and protocolManual reconnection logic neededSupports binary and text framesPick SSE for simple push; WebSockets for bidirectional low-latencyTHECODEFORGE.IO
thecodeforge.io
SSE vs WebSockets Tradeoffs
Long Polling Sse Websockets

Production Gotchas: What Will Burn You

  1. 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).
  2. 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).
  3. 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.
  4. 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.
Interview Gold: Reconnection Storm Mitigation
When a server restarts, all clients reconnect at once. This can cause a thundering herd. Mitigation: use exponential backoff with random jitter. For SSE, set the 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.
● Production incidentPOST-MORTEMseverity: high

The 3 AM Thread Pool Exhaustion

Symptom
A payments service stopped processing new transactions. The server was alive but all request threads were blocked. Error: 'java.util.concurrent.RejectedExecutionException: Thread pool exhausted'
Assumption
The team assumed a traffic spike caused the thread pool to fill up.
Root cause
Long polling endpoints held HTTP connections open for 60 seconds each. With 200 concurrent clients, the thread pool (size 200) was fully occupied by long polling requests. New payment requests couldn't get a thread.
Fix
Moved to SSE with a dedicated event loop (Netty). SSE uses a single thread per connection for writing, not per request. Thread pool freed for business logic.
Key lesson
  • 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.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Clients not receiving updates after a server restart (SSE or WebSocket)
Fix
1. Check if the server is running and accepting connections. 2. Verify the client's reconnection logic: for SSE, check the EventSource readyState; for WebSocket, check if onopen fires. 3. Ensure the server sends a last-event-id or sequence number so clients can resume from where they left off.
Symptom · 02
WebSocket connections failing with 'WebSocket is closed before the connection is established'
Fix
1. Check if the load balancer or proxy supports WebSocket upgrades (look for 'Upgrade: websocket' header). 2. Verify the server's WebSocket library is configured correctly (e.g., ws library in Node.js). 3. Test with a direct connection bypassing the proxy. 4. If proxy doesn't support WebSocket, fall back to SSE or long polling.
Symptom · 03
High server CPU due to many long polling connections
Fix
1. Check the number of active connections: 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.
★ Long Polling vs SSE vs WebSockets Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
SSE connection drops after 60 seconds of inactivity
Immediate action
Check if the server sends keepalive comments
Commands
curl -N http://localhost:3001/events
tcpdump -i any port 3001 -A | grep 'keepalive'
Fix now
Add res.write(': keepalive\n\n'); in a setInterval every 15 seconds.
WebSocket connection fails with 426 Upgrade Required+
Immediate action
Check if the load balancer supports WebSocket
Commands
curl -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' http://yourdomain.com
Check proxy logs for 'Upgrade' header stripping
Fix now
Configure load balancer to pass through WebSocket upgrades (e.g., AWS ALB: enable WebSocket support in target group).
Long polling requests timing out and clients see 503+
Immediate action
Check server thread pool usage
Commands
jstack <pid> | grep 'http-nio' | wc -l
netstat -an | grep :80 | grep ESTABLISHED | wc -l
Fix now
Increase thread pool size or switch to async I/O. For Tomcat: set maxThreads="500" in server.xml. Better: use SSE.
Client receives duplicate events after reconnection (SSE)+
Immediate action
Check if the server handles Last-Event-ID header
Commands
curl -H 'Last-Event-ID: 42' http://localhost:3001/events
Check server logs for the header value
Fix now
Implement idempotent event replay: if Last-Event-ID is present, skip events with id <= that value.
Feature / AspectLong PollingSSEWebSockets
DirectionClient-initiated (simulated push)Server-to-client onlyFull-duplex
TransportHTTP/1.1 (multiple requests)HTTP/1.1 (persistent connection)WebSocket protocol (upgraded HTTP)
LatencyHigh (poll interval + network)Low (server pushes immediately)Very low (persistent TCP)
Browser supportAll browsersAll modern browsers (IE11+ with polyfill)All modern browsers
Concurrent connections per domainUnlimited (but each ties up a thread)6 (browser limit)255 (browser limit)
Auto-reconnectNo (must re-poll manually)Yes (EventSource with last-event-id)No (must implement manually)
Binary dataYes (base64 or multipart)No (text only, base64 for binary)Yes (native binary frames)
ComplexityLowLowMedium-High
Proxy friendlinessHigh (plain HTTP)High (plain HTTP)Low (requires upgrade header)
ScalabilityPoor (thread-per-request)Good (async I/O)Good (async I/O)

Key takeaways

1
Long polling is a hack that scales poorly; avoid it unless you have no other option.
2
SSE is simpler than WebSockets for unidirectional streaming and gives you auto-reconnect with last-event-id for free.
3
WebSockets are the only choice for bidirectional low-latency communication, but require manual reconnection and heartbeat handling.
4
Always send keepalive pings for persistent connections (SSE comments, WebSocket ping/pong) to prevent proxy timeouts and detect dead clients.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does SSE handle reconnection differently from WebSockets, and what a...
Q02SENIOR
When would you choose SSE over WebSockets for a real-time dashboard?
Q03SENIOR
What happens to a WebSocket connection when the server restarts? How do ...
Q04JUNIOR
What is long polling and why is it considered a hack?
Q05SENIOR
A production incident: your SSE-based notification system stops deliveri...
Q06SENIOR
How would you design a real-time chat system that supports 1 million con...
Q01 of 06SENIOR

How does SSE handle reconnection differently from WebSockets, and what are the implications for message ordering?

ANSWER
SSE's EventSource API automatically reconnects and sends a Last-Event-ID header. The server can replay missed events, ensuring in-order delivery. WebSockets require manual reconnection logic; you must track message IDs and replay from the last acknowledged message. SSE gives you ordered delivery for free; WebSockets require you to build it.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What's the difference between long polling, SSE, and WebSockets?
02
When should I use SSE instead of WebSockets?
03
How do I implement SSE reconnection with last-event-id?
04
Can WebSockets work through a corporate proxy?
N
Naren Founder & Principal Engineer

20+ years shipping large-scale distributed systems. Everything here is grounded in real deployments.

Follow
Verified
production tested
June 25, 2026
last updated
1,663
articles · all by Naren
🔥

That's Networking. Mark it forged?

3 min read · try the examples if you haven't

Previous
WebSockets Explained
6 / 7 · Networking
Next
IP Addressing and Anycast