Before WebSockets and SSE, the only way to “push” data from server to client over HTTP was polling. We still use polling today for fallbacks, simple cases, and quick prototypes.
In simple language: short polling = “anything new yet?” every X seconds. Long polling = “tell me when something’s new, I’ll wait.”
Short Polling
The client sends a request on a fixed interval. The server replies immediately with whatever it has (often an empty list).
// Client
async function pollMessages() {
const res = await fetch("/messages?since=" + lastId);
const data = await res.json();
if (data.length) renderMessages(data);
setTimeout(pollMessages, 5000); // ask again in 5s
}
pollMessages();
Pros:
- Dead simple. Just plain HTTP.
- Works through any proxy or firewall.
- Easy to scale — every request is independent.
Cons:
- Wastes bandwidth — most requests return nothing.
- Latency — new data sits up to
intervalseconds before the client sees it. - More requests = more load on the server, even when nothing’s happening.
Long Polling
The client sends a request, but the server doesn’t reply until it has data (or a timeout hits, e.g. 30s). When the client gets a response, it immediately sends another request.
// Client
async function longPoll() {
try {
const res = await fetch("/messages?since=" + lastId);
const data = await res.json();
if (data.length) {
renderMessages(data);
lastId = data[data.length - 1].id;
}
} catch (e) {
await new Promise(r => setTimeout(r, 1000)); // backoff on error
}
longPoll(); // immediately reconnect
}
longPoll();
// Server (Express, simplified)
app.get("/messages", async (req, res) => {
const since = parseInt(req.query.since || "0");
// Wait for new data, or timeout after 30s
const messages = await waitForMessagesSince(since, 30_000);
res.json(messages); // returns [] on timeout
});
Pros:
- Near real-time — the server replies the instant new data arrives.
- Far fewer empty responses than short polling.
Cons:
- The server has to hold open many connections simultaneously. Needs an async/event-loop server (Node, Go, async Python). Not great for thread-per-request servers.
- Still has per-request overhead (headers, TLS handshake if no keep-alive).
- Reconnection adds a tiny race window where messages could land between requests — protocols use
since=lastIdcursors to avoid losing them.
Trade-Off Summary
| Aspect | Short polling | Long polling |
|---|---|---|
| Latency | Up to interval | Near real-time |
| Server load | Lots of empty replies | Fewer requests, more held connections |
| Server type | Any | Async (Node, Go, etc.) |
| Bandwidth | Wasted on empty responses | Efficient when idle |
| Complexity | Trivial | Slight (timeout + cursor) |
When to Pick What
- Short polling — low-frequency updates (1 per minute is fine), or when we just need something working in 10 lines of code.
- Long polling — chat-like scenarios where we need fast updates but can’t use WebSockets (legacy networks, restrictive proxies).
- WebSockets / SSE — anything serious in 2026. Polling is mostly a fallback now.
Common Gotchas
- Don’t poll faster than necessary. A 1-second short poll on a busy app crushes the server. Match the interval to how stale the data can be.
- Backoff on errors. If the server is down, polling every second from 100k clients amounts to a self-DDoS.
- Use a cursor. Always tell the server what we last saw (
since=42) so it doesn’t resend the same data.
Interview Tip
If asked “how would we build a chat app without WebSockets,” walk through long polling. Mention the timeout, the cursor, and the need for an async server. Bonus: explain why long polling is harder to scale — each waiting client holds a TCP connection.