Cruxly › System design › Scaling Building Blocks
Long-Polling, WebSockets, and Server-Sent Events
the server has news — how does the client find out
Open the interactive lessonPlain HTTP is a client asking and a server answering, once. The moment a design needs the server to tell the client something — a new chat message, a live score, a notification — that one-shot shape stops fitting, and you have four real options with genuinely different costs.
The baseline: plain request/response, and why it doesn't fit
Plain HTTP is one round trip: the client asks, the server answers, done. The server can only respond to a request that already arrived, so it can't push news on its own. Every mechanism below works around that one gap and pays a different price.
AJAX polling: ask again on a timer, and pay for every empty answer
The client asks again on a fixed interval, say every 3 seconds. It needs nothing beyond plain request/response, so it's usually the first thing people try.
The cost is the wasted round trip. Most polls find nothing new, yet each one pays for connection setup, headers and a server hit.
- Poll slowly and updates feel laggy.
- Poll quickly and you've built a denial-of-service against your own servers, multiplied by every connected client.
HTTP long-polling: the "hanging GET"
The server holds the request open and answers only when it has data. If a timeout fires first, it answers empty just to close the request out. Either way the client reopens a new request at once. This is the "hanging GET".
| Plain polling | Long-polling | |
|---|---|---|
| Empty answers | Most responses | Only when the timeout fires |
| Server cost | Many short requests | One held-open connection per waiting client |
Holding that many idle connections is a real resource commitment that most request-handling stacks weren't built for at scale.
WebSockets: one handshake, then a real two-way pipe
One H T T P handshake sets up a single TCP connection, which upgrades to a full-duplex channel: both sides push at any time, with no new request per message. It's the only mechanism here where the client pushes on the same channel.
Messages don't each carry fresh HTTP headers, so per-message overhead is much lower than polling. You pay the handshake once. It fits chat, multiplayer state, collaborative editing and live trading data.
:::key The one sentence that distinguishes it from everything else here One handshake, then push in either direction, on the same connection, indefinitely. Long-polling and SSE are still request-response underneath; WebSockets are not. :::
Server-Sent Events: WebSockets' one-way cousin
The server keeps one persistent HTTP connection open and writes each new event onto it. The client's EventSource keeps receiving, with no reopening and no repeated headers. It is server-to-client only. A client-to-server leg must be a separate ordinary request. SSE fits streams the client only watches: a live score ticker, a progress feed, notifications, a metrics dashboard. If the client must talk back on the same channel, use WebSockets.
Choose by direction of flow first:
| Need | Pick |
|---|---|
| Server to client, occasional, latency-tolerant | Polling: simplest to build |
| Server to client, frequent, latency-sensitive | SSE |
| Both directions, real-time | WebSockets |
| Proxies or firewalls block SSE and WebSockets | Long-polling as a fallback (Socket.IO falls back to it automatically). It holds a connection like SSE does, so it saves no server resources over SSE |
The questions that come next, at scale:
| Problem | Answer |
|---|---|
| Which server holds the recipient's connection? | Keep a registry (user to server) and a pub/sub backplane (Redis pub/sub, NATS, Kafka) to forward the message to the right server |
| Idle connections get cut | Load balancers and proxies close idle connections (often after about 60 seconds), so send heartbeats or pings |
| A deploy disconnects everyone | Clients reconnecting at once cause a reconnection storm. Use jittered backoff and drain servers gradually |
| SSE extras | EventSource reconnects automatically and sends Last-Event-ID, so the server resumes the stream. HTTP/1.1 allows only about 6 connections per domain, which SSE tabs can exhaust; HTTP/2 multiplexing removes that limit |
How to say it in the interview
"The question I actually answer first is which direction data needs to flow. If it's server-to-client only, I'd reach for SSE over plain HTTP — it's the lightest mechanism that fits. The moment the client also needs to push, it has to be WebSockets, because SSE genuinely cannot do that half of the conversation."
Checklist
- Named the baseline limitation plain HTTP has — the server can't speak first
- Identified AJAX polling's actual cost as the wasted round trip on empty answers
- Described long-polling as a held-open request that only answers on data or timeout
- Named WebSockets' defining property as full-duplex over one connection after one handshake
- Named SSE's defining limitation as unidirectional, server-to-client only
- Chose between the four based on direction of flow and frequency, not familiarity
- Said what each mechanism gives up to get its simplicity