Four Ways to Get Fresh Data to a Client, and When to Use Each
Real-Time Options for Pushing Data to Clients
Short polling, long polling, Server-Sent Events, and WebSockets — how each works, what they cost, and how to choose between SSE and WebSockets.
What you'll learn
- Explain how polling, long polling, SSE, and WebSockets deliver updates
- Compare them on latency, overhead, direction, and complexity
- Decide between SSE and WebSockets for a given feature
Standard HTTP is pull: the client asks, the server answers, the connection closes. But plenty of features need push — a new chat message, a live score, a “your order shipped” toast. Since the server can’t dial an arbitrary browser, we have a handful of techniques that simulate or enable server push. Here are the four, from crudest to most capable.
Short polling
The client asks “anything new?” on a fixed timer — say every 5 seconds.
Dead simple, works everywhere, no special server. But it’s wasteful: most requests return nothing, every poll pays a full request/header round trip, and your update latency is bounded by the poll interval (poll every 5s, updates can be up to 5s stale). Fine for low-stakes, low-frequency data; bad at scale.
Long polling
Smarter: the client makes a request, and the server holds it open until there is something to send (or a timeout fires). The instant data arrives, the server responds; the client immediately opens the next request.
This cuts the empty responses and gets near-real-time latency over ordinary HTTP, with no new protocol. The cost is many hanging connections — each waiting client ties up a connection — and the reconnect churn after every message. It was the workhorse before SSE and WebSockets and is still a solid fallback.
Server-Sent Events (SSE)
SSE is a one-way streaming channel built into HTTP. The client opens a GET
with Accept: text/event-stream; the server keeps the response open and writes
events as plain text whenever it likes. The browser’s EventSource API handles
parsing and automatic reconnection for you.
// --- server (Express): one long-lived response, many events ---
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
const timer = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`); // one event
}, 1000);
req.on('close', () => clearInterval(timer)); // clean up when client leaves
});
// --- browser: reconnects automatically on drop ---
const es = new EventSource('/events');
es.onmessage = (e) => console.log(JSON.parse(e.data)); SSE is server-to-client only, text-only, and runs over plain HTTP — so it passes through proxies cleanly and gets multiplexing on HTTP/2. It’s the perfect fit for feeds, notifications, live dashboards, and progress streams where the client only needs to listen.
WebSockets
WebSockets give you a full-duplex connection: after an HTTP Upgrade
handshake, the TCP connection is repurposed into a persistent two-way channel
where either side sends messages (text or binary) at any time, with minimal
framing overhead.
This is what you want for chat, multiplayer games, collaborative editing, live trading — anything where the client also sends frequent messages. The price is operational: a stateful long-lived connection per client, which (as the next lesson shows) is genuinely hard to scale across many server instances.
The comparison
| Technique | Direction | Latency | Overhead | Complexity | Auto-reconnect |
|---|---|---|---|---|---|
| Short polling | Pull | Poll interval | High (empty requests) | Trivial | N/A |
| Long polling | Pull (held) | Near real-time | Medium (reconnects) | Low | Manual |
| SSE | Server → client | Real-time | Low | Low | Built in |
| WebSockets | Bidirectional | Real-time | Lowest per message | High | Manual |
SSE vs WebSockets: the real decision
Most “do I need WebSockets?” questions are actually “SSE or WebSockets?” The rule of thumb:
- Client only needs to receive? Use SSE. It’s simpler, auto-reconnects, rides plain HTTP, and survives proxies and HTTP/2 multiplexing without fuss.
- Client also needs to send frequently, with low latency? Use WebSockets. Two-way, binary-capable, lowest per-message overhead.
When you do need true bidirectional real-time at scale, the hard part isn’t opening a socket — it’s that those sockets are stateful. That problem is big enough to get its own lesson, next.