Four points on one spectrum β short polling, long polling, SSE, and WebSockets β trading network overhead and complexity for lower latency.
"How does the client learn about new data?" has four common answers. They form a ladder: each rung cuts latency and wasted requests, and adds a bit of complexity or infrastructure demand.
The client asks on a fixed interval. Dead simple, works everywhere, and usually wrong for realtime.
setInterval(async () => {
const r = await fetch("/api/messages?since=" + lastId);
const items = await r.json();
if (items.length) render(items);
}, 3000); // up to 3s stale, and a request every 3s even when nothing changed
The client makes a request; the server holds it open until data is available (or a timeout), then responds. The client immediately re-requests. Latency approaches push, using only plain HTTP.
async function longPoll() {
try {
// server blocks up to ~30s waiting for new data, then returns
const r = await fetch("/api/updates?since=" + lastId, { signal: timeout(35000) });
if (r.status === 200) { const d = await r.json(); lastId = d.id; render(d); }
} catch (_) { /* timeout β just loop */ }
longPoll(); // reconnect immediately
}
longPoll();
The top two rungs keep a single connection open and stream over it β no per-message reconnect. SSE streams one-way over HTTP with built-in reconnect; WebSockets add a return channel. Full detail on their own pages.
| Short poll | Long poll | SSE | WebSockets | |
|---|---|---|---|---|
| Latency | Up to interval | ~Instant | ~Instant | ~Instant |
| Wasted requests | High | Low | None | None |
| Direction | Pull | Pull (emulated push) | Server push | Full-duplex |
| Connections held | None (bursty) | One per client | One per client | One per client |
| Client complexity | Trivial | Low | Low | Higher |
| Server model | Stateless | Held requests | Streaming responses | Stateful sockets |
| Reconnect/resume | N/A | Manual | Built in | Manual |