WebSockets vs HTTP Polling
Polling gets the job done until it doesn't. Here's an honest comparison of WebSockets and HTTP polling — latency, server load, complexity, and when to use each.
When you need to show users data that changes — new messages, live prices, updated order status — you have a fundamental choice: ask the server repeatedly, or keep a connection open and let the server tell you.
Both approaches work in production today. Neither is categorically better. The right choice depends on your update frequency, user count, infrastructure, and tolerance for complexity.
How HTTP Polling Works
Polling is the oldest realtime-adjacent technique. The client sends HTTP requests on a schedule and the server responds with whatever is current.
Short Polling
The client fires a request every N seconds regardless of whether data has changed:
setInterval(async () => {
const res = await fetch('/api/messages/latest');
const data = await res.json();
if (data.messages.length > 0) {
renderMessages(data.messages);
}
}, 3000);
Simple to implement. Works everywhere. The downside: most requests return nothing, and latency is bounded by your interval. With a 3-second poll, users wait up to 3 seconds to see a new message.
Long Polling
Long polling reduces wasteful requests by having the server hold the request open until it has something to return (or a timeout expires):
async function longPoll() {
const res = await fetch('/api/messages/wait?since=1234567890');
const data = await res.json();
renderMessages(data.messages);
longPoll(); // immediately start the next request
}
longPoll();
The server side blocks until new data is available:
app.get('/api/messages/wait', async (req, res) => {
const { since } = req.query;
const messages = await waitForMessages(since, { timeout: 30000 });
res.json({ messages });
});
Long polling achieves lower latency than short polling while reducing empty responses. The cost is that your server must handle many open HTTP connections simultaneously — essentially the same scaling problem as WebSockets, but with HTTP overhead on every reconnect.
How WebSockets Work
A WebSocket connection starts as an HTTP request and upgrades to a persistent TCP connection:
GET /socket HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
After the handshake, both sides can send messages at any time over the same connection. No polling, no reconnecting between messages, no HTTP overhead per event.
const ws = new WebSocket('wss://api.example.com/socket');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
renderMessage(data);
};
// Server can push at any time — no request needed
Head-to-Head Comparison
| Short Polling | Long Polling | WebSockets | |
|---|---|---|---|
| Latency | Up to interval | Near-real-time | Real-time (ms) |
| Server connections | Low (brief) | High (held open) | High (persistent) |
| HTTP overhead | Every request | Every reconnect | Handshake only |
| Bidirectional | No | No | Yes |
| Browser support | Universal | Universal | Universal (IE10+) |
| Proxy issues | Rare | Rare | Occasional |
| Implementation complexity | Low | Medium | Medium–High |
| Scaling complexity | Low | Medium | High |
When Polling Is Actually Fine
Polling gets unfairly dismissed. For many use cases it's the right choice.
Low update frequency. If your data changes once a minute, a 30-second poll interval is perfectly reasonable. There's no meaningful benefit to a persistent connection that delivers updates every 30 seconds.
Simple infrastructure. Polling works with any HTTP server, CDN, serverless function, or proxy — no special WebSocket support required. On platforms that don't support persistent connections (some serverless environments, certain API gateways), polling may be your only option.
Short-lived sessions. If users interact with your UI for 2–3 minutes and then leave, the overhead of establishing and maintaining WebSocket connections may exceed the benefits.
Low concurrency. At 100 concurrent users, polling overhead is negligible. At 100,000 concurrent users, empty polling requests represent meaningful server load.
When WebSockets Win
Chat and messaging. Users expect messages instantly. A 3-second polling delay is perceptible and frustrating. WebSockets deliver in tens of milliseconds.
Collaborative editing. Multiple users editing simultaneously requires low-latency, bidirectional updates. Polling at any reasonable interval makes collaboration feel laggy.
Live dashboards. Financial data, operational metrics, or live sports scores that update many times per second would generate enormous polling traffic. WebSockets push only on change.
Multiplayer and gaming. Real-time game state synchronization requires sub-100ms latency. Polling cannot approach this.
Presence and typing indicators. These are transient, high-frequency signals. Polling at the rate required to make them feel real (100–500ms intervals) generates far more load than a single persistent connection.
SSE as a Middle Ground
Server-Sent Events (SSE) sit between polling and WebSockets. The client opens a persistent HTTP connection and the server streams events over it — but communication is one-directional (server to client only).
const events = new EventSource('/api/stream');
events.onmessage = (event) => {
const data = JSON.parse(event.data);
renderUpdate(data);
};
SSE works over standard HTTP/2, multiplexes well, and automatically reconnects. If you only need server-to-client push — notifications, live feeds, order status — SSE is often simpler than WebSockets and works transparently through proxies and load balancers that might struggle with the WebSocket upgrade.
The Practical Recommendation
- Data changes less than once per minute: short polling, 30–60 second interval
- Data changes frequently, server-to-client only: SSE
- Data changes frequently, bidirectional or presence needed: WebSockets
If you choose WebSockets, managed infrastructure like Apinator handles the scaling, auth, and connection management — so you keep the developer experience without inheriting the operational complexity.