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.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
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 earlyconst idx = clients.indexOf(res);
if (idx !== -1) clients.splice(idx, 1);
});
// Timeout after 30 seconds to prevent resource leaksetTimeout(() => {
if (!res.writableEnded) {
res.end('[]'); // empty response
}
}, 30000);
} elseif (req.url === '/push') {
// Simulate pushing data to all polling clientslet 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.
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.
thecodeforge.io
Long Polling Sse Websockets
thecodeforge.io
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.jsconst 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 timeoutconst keepalive = setInterval(() => {
res.write(': keepalive\n\n');
}, 15000);
// Simulate sending eventslet 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)constWebSocket = require('ws');
const wss = newWebSocket.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 secondsconst 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.
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
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.
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.
thecodeforge.io
Long Polling Sse Websockets
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.
🔥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+
Implement idempotent event replay: if Last-Event-ID is present, skip events with id <= that value.
Feature / Aspect
Long Polling
SSE
WebSockets
Direction
Client-initiated (simulated push)
Server-to-client only
Full-duplex
Transport
HTTP/1.1 (multiple requests)
HTTP/1.1 (persistent connection)
WebSocket protocol (upgraded HTTP)
Latency
High (poll interval + network)
Low (server pushes immediately)
Very low (persistent TCP)
Browser support
All browsers
All modern browsers (IE11+ with polyfill)
All modern browsers
Concurrent connections per domain
Unlimited (but each ties up a thread)
6 (browser limit)
255 (browser limit)
Auto-reconnect
No (must re-poll manually)
Yes (EventSource with last-event-id)
No (must implement manually)
Binary data
Yes (base64 or multipart)
No (text only, base64 for binary)
Yes (native binary frames)
Complexity
Low
Low
Medium-High
Proxy friendliness
High (plain HTTP)
High (plain HTTP)
Low (requires upgrade header)
Scalability
Poor (thread-per-request)
Good (async I/O)
Good (async I/O)
⚙ Quick Reference
3 commands from this guide
File
Command / Code
Purpose
longPollServer.js
const http = require('http');
Long Polling
sseServer.js
const http = require('http');
SSE
wsServer.js
const WebSocket = require('ws');
WebSockets
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.
Q02 of 06SENIOR
When would you choose SSE over WebSockets for a real-time dashboard?
ANSWER
Choose SSE when the data flow is unidirectional (server to client) and you want simplicity. SSE's auto-reconnect and last-event-id reduce client code. Avoid SSE if you need to send commands from the client to the server (bidirectional) or if you need binary data. Also, if you need more than 6 concurrent connections per domain, WebSockets are better.
Q03 of 06SENIOR
What happens to a WebSocket connection when the server restarts? How do you mitigate data loss?
ANSWER
The TCP connection breaks. The client's onclose fires. Without reconnection logic, the client stays disconnected. Mitigation: implement client-side reconnection with exponential backoff. Use message IDs and a buffer on the server to replay missed messages. Also, use a heartbeat to detect half-open connections and clean them up.
Q04 of 06JUNIOR
What is long polling and why is it considered a hack?
ANSWER
Long polling is a technique where the client sends an HTTP request, and the server holds it open until it has data to send. It's a hack because it simulates push over a request-response protocol. It's inefficient: each poll is a full HTTP request with headers and TLS overhead, and it ties up server resources for the duration of the poll.
Q05 of 06SENIOR
A production incident: your SSE-based notification system stops delivering events after a few hours. Clients are connected but receive nothing. What do you check?
ANSWER
First, check if the server is still sending data. Use curl to connect to the SSE endpoint and see if events arrive. If not, check server logs for errors. Second, check for proxy timeouts: load balancers often have a 60-second idle timeout. Ensure the server sends keepalive comments every 15 seconds. Third, check the server's connection count: if it hits the file descriptor limit, new connections are rejected.
Q06 of 06SENIOR
How would you design a real-time chat system that supports 1 million concurrent users?
ANSWER
Use WebSockets for bidirectional low-latency communication. Use an event-driven architecture with a message broker (e.g., Redis Pub/Sub or Kafka) to fan out messages to multiple server instances. Each server maintains a map of user ID to WebSocket connection. Use sticky sessions or a global connection registry (e.g., Redis) to route messages to the correct server. Implement horizontal scaling with a load balancer that supports WebSocket upgrades. Use heartbeats to detect dead connections and clean up. For persistence, store messages in a database and use message IDs for reliable delivery.
01
How does SSE handle reconnection differently from WebSockets, and what are the implications for message ordering?
SENIOR
02
When would you choose SSE over WebSockets for a real-time dashboard?
SENIOR
03
What happens to a WebSocket connection when the server restarts? How do you mitigate data loss?
SENIOR
04
What is long polling and why is it considered a hack?
JUNIOR
05
A production incident: your SSE-based notification system stops delivering events after a few hours. Clients are connected but receive nothing. What do you check?
SENIOR
06
How would you design a real-time chat system that supports 1 million concurrent users?
SENIOR
FAQ · 4 QUESTIONS
Frequently Asked Questions
01
What's the difference between long polling, SSE, and WebSockets?
Long polling is a hack where the client repeatedly sends HTTP requests that the server holds open. SSE is a standard for server-to-client streaming over a single HTTP connection. WebSockets provide a persistent full-duplex connection. Use WebSockets for bidirectional low-latency, SSE for unidirectional streaming, and long polling only when you can't use the others.
Was this helpful?
02
When should I use SSE instead of WebSockets?
Use SSE when you only need server-to-client data (e.g., notifications, live feeds) and want simplicity. SSE's EventSource API handles reconnection and last-event-id automatically. Avoid SSE if you need bidirectional communication, binary data, or more than 6 concurrent connections per domain.
Was this helpful?
03
How do I implement SSE reconnection with last-event-id?
The EventSource API automatically reconnects and sends a Last-Event-ID header with the last received event id. On the server, parse this header and replay events from that id onward. For example, in Node.js: const lastId = req.headers['last-event-id']; then skip events with id <= lastId.
Was this helpful?
04
Can WebSockets work through a corporate proxy?
Often not. Corporate proxies may strip the Upgrade header required for WebSocket handshake. If you need to support such environments, fall back to SSE or long polling. Alternatively, use a WebSocket library that supports HTTP long polling as a fallback transport (e.g., Socket.IO).