Not every real-time feature needs WebSockets. Sometimes we just need the server to push updates to the client — new notifications, live scores, order status changes. Before reaching for WebSockets, we should consider simpler alternatives: polling, long polling, and Server-Sent Events. Each has its sweet spot.
Short Polling
The simplest approach. The client asks the server “any updates?” at regular intervals.
// Client polls every 5 seconds
setInterval(async () => {
const response = await fetch('/api/notifications');
const data = await response.json();
if (data.length > 0) {
showNotifications(data);
}
}, 5000);
Think of it like checking our mailbox every 5 minutes. Most of the time, there’s nothing new. But we keep walking to the mailbox and back anyway.
Pros:
- Dead simple to implement
- Works everywhere (it’s just regular HTTP)
- Stateless — server doesn’t track connections
Cons:
- Wasteful — most requests return “nothing new”
- Delay between events — if something happens right after we poll, we don’t know for 5 seconds
- Scales poorly — 10,000 clients polling every 5 seconds = 2,000 requests per second for nothing
Long Polling
A smarter version of polling. The client sends a request, and the server holds it open until there’s new data. When data arrives, the server responds. The client immediately sends another request.
// Client long-polls
async function longPoll() {
try {
const response = await fetch('/api/notifications/subscribe');
const data = await response.json();
showNotifications(data);
} catch (err) {
// wait a bit before retrying on error
await new Promise(r => setTimeout(r, 3000));
}
// immediately poll again
longPoll();
}
longPoll();
Think of it like calling a restaurant and asking “is my table ready?” Instead of them saying “no” and hanging up (short polling), they say “hold on, I’ll let you know when it’s ready” and keep us on the line.
Pros:
- Near real-time — data arrives as soon as the server has it
- No wasted requests — each request results in data
- Works through firewalls and proxies (it’s still HTTP)
Cons:
- Server holds open connections — each waiting client ties up server resources
- Not truly bidirectional — client can’t push data while waiting
- Timeout handling — if the server holds too long, proxies/load balancers may kill the connection (typically 30-60 second timeout)
Server-Sent Events (SSE)
SSE is a standardized way for the server to push updates to the client over a single, long-lived HTTP connection. The client opens the connection once, and the server sends events whenever it wants.
// Client — SSE is built into browsers
const eventSource = new EventSource('/api/notifications/stream');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
showNotification(data);
};
eventSource.onerror = () => {
console.log('Connection lost, browser will auto-reconnect');
};
Server side (Node.js):
app.get('/api/notifications/stream', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
// Send a notification whenever we have one
const sendEvent = (data) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// Subscribe to notification events
notificationEmitter.on('notify', sendEvent);
req.on('close', () => {
notificationEmitter.off('notify', sendEvent);
});
});
The server sends data in a specific format:
data: {"type": "notification", "message": "New follower!"}
data: {"type": "notification", "message": "Your order shipped!"}
event: heartbeat
data: ping
Pros:
- Built into browsers —
EventSourceAPI with automatic reconnection - Simple — just HTTP with a special content type
- Efficient — single connection, server pushes when ready
- Auto-reconnect — the browser handles reconnection automatically
Cons:
- One-directional — server to client only. Client can’t send data over the same connection.
- Connection limits — browsers limit SSE connections per domain (6 in HTTP/1.1, more in HTTP/2)
- Text only — no binary data (but we can base64 encode if needed)
Comparison: All Four Approaches
| Feature | Short Polling | Long Polling | SSE | WebSocket |
|---|---|---|---|---|
| Direction | Client → Server | Client → Server | Server → Client | Both ways |
| Latency | Up to poll interval | Near real-time | Real-time | Real-time |
| Complexity | Very low | Low | Low | Medium-High |
| Server resources | Low per request | Medium (held connections) | Medium (held connections) | Medium (held connections) |
| Browser support | Everywhere | Everywhere | All modern browsers | All modern browsers |
| Auto-reconnect | N/A | Manual | Built-in | Manual |
| Binary data | Yes | Yes | No (text only) | Yes |
| Scalability | Poor (wasted requests) | OK | Good | Good |
| Firewall friendly | Yes | Yes | Yes | Sometimes no |
When to Use Each
Short Polling — When updates are infrequent and slight delay is fine. Weather data, email checking. Dead simple to implement.
Long Polling — When we need near real-time but can’t use WebSocket or SSE. Good fallback. Used by older chat systems.
SSE — When we only need server-to-client push. Live notifications, news feeds, stock tickers, progress bars, live logs. This is the underrated option — it’s simpler than WebSocket and handles 90% of “real-time” use cases.
WebSocket — When we need full bidirectional communication. Chat, multiplayer games, collaborative editing. Worth the extra complexity only when both sides need to send data frequently.
A Real-World Decision Tree
Do we need real-time updates?
├─ No → Regular HTTP (REST)
└─ Yes → Does the client need to send data too?
├─ No → SSE (simplest real-time option)
└─ Yes → How often does the client send?
├─ Rarely → SSE + regular HTTP POST for client messages
└─ Frequently → WebSocket
The often-overlooked option is SSE + REST. We use SSE for the server pushing updates and regular POST requests when the client needs to send something. This gives us 80% of WebSocket functionality with way less complexity.
Key Takeaway
In simple language, polling is the client repeatedly asking “anything new?” — simple but wasteful. Long polling improves this by having the server wait until there IS something new. SSE opens a one-way channel where the server pushes updates as they happen. WebSocket opens a two-way channel for full real-time communication. Start with the simplest option that meets our needs — often that’s SSE, not WebSocket.