new EventSource('/events') for SSE; new WebSocket('wss://host/ws') for WebSocket| Feature | Short Polling | Long Polling | SSE | WebSocket | WebTransport |
|---|---|---|---|---|---|
| Direction | Client-to-server | Client-to-server | Server-to-client | Bidirectional | Bidirectional |
| Latency | High (poll interval) | Medium (held request) | Low (~same as WS) | Low (~3ms advantage) | Lowest (UDP optional) |
| Per-message overhead | Full HTTP headers (~800B) | Full HTTP headers (~800B) | ~5 bytes/message | ~2 bytes/frame | ~2 bytes/frame |
| Auto-reconnect | N/A | Manual | Built-in (EventSource) | Manual | Manual |
| Binary support | Yes (via encoding) | Yes (via encoding) | No (UTF-8 text only) | Yes (native) | Yes (native + datagrams) |
| HTTP/2 multiplexing | Yes | Yes | Yes (removes 6-conn limit) | No (separate TCP) | Yes (QUIC streams) |
| Browser support | Universal | Universal | 97%+ (no IE) | 98%+ | Chromium only (experimental) |
| Proxy friendliness | Excellent | Good | Excellent (standard HTTP) | Poor (needs upgrade) | Poor (needs HTTP/3) |
| Scaling difficulty | Low (stateless) | High (held connections) | Medium (long-lived HTTP) | High (stateful connections) | Medium (QUIC) |
| Connection setup | New HTTP request each time | New HTTP request each time | Single HTTP stream | HTTP upgrade to TCP | HTTP/3 CONNECT |
| Max connections/domain | Browser-limited | Browser-limited | 6 (HTTP/1.1), unlimited (HTTP/2) | Browser-limited | Browser-limited |
| Ideal use case | Dashboards with 30s+ intervals | Legacy compatibility | Live feeds, notifications | Chat, gaming, collab editing | Future: gaming, streaming |
START
|-- Does data flow only from server to client?
| |-- YES --> Is HTTP/2 available?
| | |-- YES --> Use SSE (EventSource API)
| | |-- NO --> Will you have >6 tabs open to same domain?
| | |-- YES --> Use WebSocket (to avoid 6-connection limit)
| | |-- NO --> Use SSE
| |-- NO (bidirectional needed) --> Is sub-second latency critical?
| |-- YES --> Are corporate proxies / firewalls a concern?
| | |-- YES --> Use WebSocket over WSS (port 443) with long-polling fallback
| | |-- NO --> Use WebSocket
| |-- NO --> Can you use request-response for client-to-server?
| |-- YES --> Use SSE for server push + regular HTTP POST for client messages
| |-- NO --> Use WebSocket
|
|-- Must support IE11 or very old browsers?
| |-- YES --> Use long polling (or polyfill EventSource)
| |-- NO --> See above
|
|-- Need unreliable/unordered datagrams (gaming, media)?
|-- YES --> Use WebTransport (with WebSocket fallback)
|-- NO --> See above
Create a server endpoint that sets the correct headers and streams events. The Content-Type must be text/event-stream, and you must disable response buffering. [src1]
// Node.js (Express) SSE server
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no'
});
res.write(':ok\n\n');
const interval = setInterval(() => {
const data = JSON.stringify({ time: new Date().toISOString() });
res.write(`id:${Date.now()}\nevent:update\ndata:${data}\n\n`);
}, 1000);
req.on('close', () => { clearInterval(interval); res.end(); });
});
app.listen(3000);
Verify: curl -N http://localhost:3000/events → streaming event:update messages every second
The browser's built-in EventSource API handles reconnection automatically. If the connection drops, it reconnects after a server-specified retry: interval (default ~3 seconds). [src1]
const source = new EventSource('/events');
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log('Received:', data);
});
source.addEventListener('error', (e) => {
if (source.readyState === EventSource.CONNECTING) {
console.log('Reconnecting...');
} else {
console.error('SSE connection failed');
source.close();
}
});
Verify: Open browser DevTools Network tab → filter by EventSource → confirm streaming responses
For bidirectional communication, set up a WebSocket server using the ws library for Node.js. [src6]
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws, req) => {
ws.on('message', (message) => {
const parsed = JSON.parse(message);
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(JSON.stringify({ from: 'server', ...parsed }));
}
});
});
ws.on('close', (code, reason) => {
console.log(`Disconnected: ${code} ${reason}`);
});
ws.send(JSON.stringify({ type: 'connected', time: Date.now() }));
});
Verify: npx wscat -c ws://localhost:8080 → type {"text":"hello"} → see broadcast response
Unlike SSE, WebSocket does not auto-reconnect. Implement reconnection with exponential backoff. [src4]
function createWebSocket(url) {
let ws, retries = 0;
const maxRetries = 10, baseDelay = 1000;
function connect() {
ws = new WebSocket(url);
ws.onopen = () => { retries = 0; };
ws.onmessage = (event) => { console.log(JSON.parse(event.data)); };
ws.onclose = (event) => {
if (!event.wasClean && retries < maxRetries) {
const delay = Math.min(baseDelay * Math.pow(2, retries++), 30000);
setTimeout(connect, delay);
}
};
}
connect();
return { getSocket: () => ws };
}
Verify: Kill and restart the server → client reconnects automatically with increasing delays
# Input: HTTP GET /events
# Output: text/event-stream with JSON events
from flask import Flask, Response
import json, time
app = Flask(__name__)
def event_stream():
while True:
data = json.dumps({"time": time.time()})
yield f"id:{int(time.time())}\nevent:update\ndata:{data}\n\n"
time.sleep(1)
@app.route('/events')
def sse():
return Response(
event_stream(),
mimetype='text/event-stream',
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}
)
if __name__ == '__main__':
app.run(port=3000, threaded=True)
# Input: WebSocket connection on ws://localhost:8765
# Output: Echoes received messages back to all clients
import asyncio, websockets, json
connected = set()
async def handler(websocket):
connected.add(websocket)
try:
async for message in websocket:
data = json.loads(message)
broadcast = json.dumps({"from": "server", **data})
await asyncio.gather(
*(c.send(broadcast) for c in connected if c.open)
)
finally:
connected.discard(websocket)
async def main():
async with websockets.serve(handler, "localhost", 8765):
await asyncio.Future()
asyncio.run(main())
// Input: HTTP GET /events
// Output: text/event-stream with periodic updates
package main
import (
"fmt"
"net/http"
"time"
)
func sseHandler(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
for {
select {
case <-r.Context().Done():
return
default:
fmt.Fprintf(w, "id:%d\nevent:update\ndata:{\"time\":\"%s\"}\n\n",
time.Now().UnixMilli(), time.Now().Format(time.RFC3339))
flusher.Flush()
time.Sleep(1 * time.Second)
}
}
}
func main() {
http.HandleFunc("/events", sseHandler)
http.ListenAndServe(":3000", nil)
}
// BAD -- WebSocket for one-way server push adds unnecessary complexity
const ws = new WebSocket('wss://api.example.com/notifications');
ws.onmessage = (e) => showNotification(JSON.parse(e.data));
// Now you need: reconnection logic, heartbeat pings, connection state
// management, proxy configuration, load balancer sticky sessions...
// GOOD -- SSE handles reconnection automatically, works through all proxies
const source = new EventSource('/notifications');
source.onmessage = (e) => showNotification(JSON.parse(e.data));
// Auto-reconnects. Works through corporate proxies. No library needed.
// BAD -- polling every 500ms wastes bandwidth and battery
setInterval(async () => {
const res = await fetch('/api/messages?since=' + lastTimestamp);
const messages = await res.json();
messages.forEach(displayMessage);
}, 500);
// 172,800 requests/day per client. Most return empty arrays.
// GOOD -- single persistent connection, instant delivery
const ws = new WebSocket('wss://chat.example.com/ws');
ws.onmessage = (e) => displayMessage(JSON.parse(e.data));
function sendMessage(text) {
ws.send(JSON.stringify({ type: 'message', text }));
}
// One connection. Near-zero latency. Minimal bandwidth.
// BAD -- connection drops silently, user sees stale data
const ws = new WebSocket('wss://example.com/ws');
ws.onmessage = handleMessage;
// Network blip? Connection gone. No recovery. User confused.
// GOOD -- automatic reconnection with backoff
function connect(url, onMessage) {
let retries = 0;
function attempt() {
const ws = new WebSocket(url);
ws.onopen = () => { retries = 0; };
ws.onmessage = onMessage;
ws.onclose = (e) => {
if (!e.wasClean) {
const delay = Math.min(1000 * 2 ** retries++, 30000);
setTimeout(attempt, delay);
}
};
}
attempt();
}
X-Accel-Buffering: no response header or set proxy_buffering off in Nginx config. [src2]proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; in Nginx. [src4]:keepalive\n\n) or ping frame (WebSocket) every 30 seconds. [src3]Last-Event-ID header. If the server ignores this, the client misses events. Fix: Track event IDs server-side and replay missed events on reconnect. [src1]close/end events and clean up resources. [src5]# Test SSE endpoint with curl (streaming)
curl -N -H "Accept: text/event-stream" http://localhost:3000/events
# Test WebSocket with wscat
npx wscat -c ws://localhost:8080
# Check if WebSocket upgrade is working through Nginx
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
http://localhost/ws
# Expected: HTTP/1.1 101 Switching Protocols
# Monitor open SSE/WebSocket connections on the server
ss -tn state established | grep :3000 | wc -l
# Check HTTP/2 support (required for SSE without 6-conn limit)
curl -I --http2 https://example.com 2>&1 | grep -i "http/2"
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Server pushes updates to browser (dashboards, feeds, alerts) | Client needs to send frequent messages to server | WebSocket |
| You want zero-library client code with auto-reconnect | You need binary data transfer (images, audio, files) | WebSocket |
| Running behind corporate proxies or strict firewalls | Building real-time multiplayer gaming | WebSocket or WebTransport |
| HTTP/2 is available and you have many concurrent streams | IE11 support required and polyfills not acceptable | Long polling |
| Building chat, collaborative editing, or live gaming | Data updates every 30+ seconds with no urgency | Short polling (simpler) |
| Both client and server send messages frequently | Proxy/firewall blocks WebSocket and WSS fallback fails | SSE + HTTP POST |
| Legacy browser/proxy environment blocks SSE and WebSocket | Latency and efficiency matter | SSE or WebSocket (with polyfill) |
visibilitychange