Real-time features are now expected in every web application — live notifications, chat, dashboards, collaborative editing. Three techniques deliver this: WebSockets (bidirectional), Server-Sent Events (server-to-client only), and Long Polling (HTTP-based fallback). Choosing the right one determines your scalability, complexity, and infrastructure costs.
⚡ TL;DR: WebSockets for bidirectional real-time (chat, gaming, collaborative editing). SSE for server-to-client streams (notifications, live feeds, AI streaming responses). Long Polling for simple real-time on infrastructure that blocks WebSockets. SSE is underrated — it is simpler than WebSockets for one-directional data.
Long Polling — the HTTP fallback
// Client: make request, server holds it until data is available or timeout
// Then client immediately makes another request
// Server (Express):
app.get('/poll', async (req, res) => {
const timeout = 30000; // 30s max hold
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
const event = await checkForNewEvents(req.user.id);
if (event) return res.json(event);
await sleep(500); // Check every 500ms
}
res.json({ type: 'timeout' }); // No event in 30s
});
// Client:
async function poll() {
while (true) {
try {
const event = await fetch('/poll').then(r => r.json());
if (event.type !== 'timeout') handleEvent(event);
} catch (e) { await sleep(2000); } // Retry on error
}
}
// Pros: works everywhere, no special infrastructure
// Cons: slow (500ms-1s latency), inefficient (constant connections)
Server-Sent Events — the underrated choice
// SSE: server pushes events over persistent HTTP connection
// One-directional: server → client only
// Built-in reconnection, event IDs, named events
// Server:
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send events
const sendEvent = (data, event = 'message', id = Date.now()) => {
res.write(`id: ${id}\n`);
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
sendEvent({ msg: 'Connected!' }, 'connected');
// Subscribe to events for this user
const unsubscribe = eventBus.subscribe(req.user.id, (event) => {
sendEvent(event, event.type);
});
// Cleanup on disconnect
req.on('close', () => unsubscribe());
});
// Client: built-in browser API with automatic reconnection
const es = new EventSource('/events');
es.addEventListener('notification', (e) => {
const data = JSON.parse(e.data);
showNotification(data);
});
es.addEventListener('error', () => {
// Auto-reconnects after 3 seconds by default
});
// SSE advantages over WebSockets for one-directional:
// - Uses regular HTTP (works through proxies, load balancers)
// - Built-in reconnection with last event ID
// - Much simpler to implement and scale
WebSockets — bidirectional real-time
// WebSockets: persistent TCP-like connection over HTTP upgrade
// True bidirectional: client ↔ server
// Server (Node.js with ws library):
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
const rooms = new Map();
wss.on('connection', (ws, req) => {
const userId = authenticateUser(req);
ws.userId = userId;
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
switch (msg.type) {
case 'join_room':
if (!rooms.has(msg.roomId)) rooms.set(msg.roomId, new Set());
rooms.get(msg.roomId).add(ws);
break;
case 'chat':
// Broadcast to room
rooms.get(msg.roomId)?.forEach(client => {
if (client !== ws && client.readyState === 1) {
client.send(JSON.stringify({ type: 'chat', msg: msg.text, from: userId }));
}
});
}
});
ws.on('close', () => {
rooms.forEach(room => room.delete(ws));
});
});
// Client:
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => ws.send(JSON.stringify({ type: 'join_room', roomId: 'general' }));
ws.onmessage = (e) => handleMessage(JSON.parse(e.data));
ws.onclose = () => setTimeout(reconnect, 1000); // Manual reconnect
Decision guide
- ✅ SSE: notifications, live feeds, AI token streaming, dashboards — simple and scalable
- ✅ WebSockets: chat, collaborative editing, multiplayer gaming, live code collaboration
- ✅ Long Polling: environments that block WebSockets, simple status updates
- ❌ Don’t use WebSockets when SSE works — WebSockets need sticky sessions or Redis pub/sub for horizontal scaling
- ❌ Don’t use Long Polling if SSE or WebSockets are available — 10x more server connections
WebSocket scaling is a system design concern — the rate limiter guide covers how to throttle WebSocket message rates. For Lambda streaming (SSE equivalent in serverless), see the Lambda Function URL streaming guide. External reference: MDN Server-Sent Events documentation.
Recommended Reading
→ Designing Data-Intensive Applications — The essential book every senior developer needs. Covers distributed systems, databases, and production architecture.
→ The Pragmatic Programmer — Timeless engineering wisdom for writing better, more maintainable code at any level.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Don’t Miss the Next Cheat Code
Join 1,000+ senior developers getting expert-level JS, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
