Long polling vs WebSockets vs SSE
Three ways to push data to a browser, with very different costs at scale.
What problem does WebSockets solve?
HTTP is built around the client asking. For anything live (a score, a message, a cursor moving) you need the server to speak first, and there is no one obvious way to do that.
The three practical options mostly differ in what they cost you per connected user, and that is the thing that decides the design.
What WebSockets costs you
Every concept in here states a cost. A technique with no price is a technique you have not understood yet — and naming the cost out loud is most of what separates a senior answer from a junior one.
When to reach for WebSockets, and when not to
Use it when
- Polling: updates are rare, or a delay does not matter. Often the right answer, and easy to overlook.
- SSE: server to client only. Notifications, live scores, progress, streaming text. It is plain HTTP and reconnects by itself.
- WebSockets: both directions and frequent. Chat, collaborative editing, games, live cursors.
- Long polling: you need push and cannot use the others. Mostly a fallback now.
Do not, when
- WebSockets for a feed that updates every 30 seconds. You are paying for a permanent connection to send almost nothing.
- SSE when the client also has to send often. It only goes one way.
- Any of them when a five-second poll would keep the user happy. Simplicity is worth real money here.
How WebSockets actually works
- —One direction: server to client. The client still posts normally over HTTP.
- —Plain HTTP, so proxies, compression and auth all behave normally.
- —Reconnects by itself and sends Last-Event-ID, so the server can resume from where the client left off.
- —Text only. Over HTTP/1.1, browsers allow about six connections per domain, shared across tabs. HTTP/2 lifts that limit.
- —Right for: notifications, live scores, progress bars, streamed responses.
- —Both directions, and very little overhead per message once connected.
- —Binary or text, and very low latency.
- —You build reconnect, resume and heartbeats yourself.
- —More infrastructure care: sticky routing, proxy config, capacity counted in connections.
- —Right for: chat, collaborative editing, live cursors, games.
Long polling works like this. The client makes a request. The server holds it open until there is something to say, or a timeout hits. Then the client immediately asks again. It works everywhere, and every message costs a full HTTP request cycle plus a reconnect.
All three share the same scaling problem. With a million connected users you are holding a million connections, spread over servers that have to find each other. When user A sends a message to user B on a different server, something has to route it. That is usually a pub/sub layer, where each server subscribes to the channels its own users care about. That layer becomes your limit, and it is where the design work in live push goes.
Deploys are the operational trap. Restart the fleet and every client reconnects at once. That is a stampede on connection setup and login, and it can be worse than the traffic you normally serve. The defences are reconnect with growing delays and randomness on the client, and rolling the fleet slowly instead of all at once.
Messages missed while disconnected are a product requirement, not a transport feature. Whatever you choose, the client should reconnect with the last id it saw and get what it missed. That means the server keeps a short buffer per channel. SSE gives you the id mechanism for free. With WebSockets you build it.
A useful hybrid: keep the live connection only for a small message saying "something changed", and have the client fetch the actual data over normal HTTP. The permanent connection carries tiny messages, the real payloads travel over cacheable requests, and you get most of the benefit for much less complexity.
- Choosing WebSockets by default because it sounds more capable.
- No reconnect-and-catch-up story, so users silently miss messages.
- Forgetting that deploys disconnect everyone at the same moment.
The WebSockets follow-up an interviewer will ask
"You have a million concurrent connections. What breaks first?"
Almost never the raw connection count on the machines. A tuned server handles a lot of idle sockets, and it comes down to memory per connection and file descriptor limits, which is a capacity planning question. What breaks first is usually the fan-out layer. When one event has to reach 100,000 subscribers spread over 200 servers, the pub/sub system doing that routing is the bottleneck, and one popular channel makes it much worse. The second thing to break is deploys. Restarting means a million reconnections and logins within a few seconds, a spike far bigger than normal traffic and entirely self-inflicted. Third is memory, if I am buffering per-connection state or missed messages. So I would group connections by channel, so a server holds users interested in the same things. Keep per-connection state tiny. Put a hard cap on buffered messages per client and drop the slow ones instead of growing without limit. And make clients reconnect with randomised, growing delays. Cost: grouping by channel means a user in many channels touches several servers, which makes routing more complex.
Your turn
A dashboard updates once a minute. Which transport, and why not the other two?
Learn it from the source
This page gets you interview-ready. These take you to where the idea is actually specified or measured — read them when you want the real depth.
- MDN — WebSockets API ↗The transport, its lifecycle, and what breaks through proxies.
- WHATWG — Server-sent events ↗The SSE specification, including automatic reconnection and Last-Event-ID.