The problem: HTTP only answers questions
Snip adds a live counter to the dashboard: during a product launch, an owner wants to watch clicks tick up as they happen. But plain HTTP is request and response. The server can only speak when the browser asks. How does the server tell the browser "another click just happened"?
There are four standard answers, from simplest to most capable.
1. Polling
The browser asks every few seconds: "any new clicks?"
GET /links/x7Kp/stats → { clicks: 18420 }
… 5 seconds later …
GET /links/x7Kp/stats → { clicks: 18420 } nothing new
… 5 seconds later …
GET /links/x7Kp/stats → { clicks: 18431 }Dead simple, works everywhere, and uses ordinary stateless servers and caches. But updates are up to one interval late, and most requests come back with nothing new. A thousand dashboards polling every 2 seconds is 500 requests a second, mostly wasted.
2. Long polling
The browser asks, and the server holds the request open until there's something to say (or a timeout of, say, 30 seconds passes), then answers. The browser immediately asks again. Updates arrive almost instantly with far fewer empty answers. The cost: every waiting browser holds an open request on a server.
3. Server-sent events (SSE)
The browser opens one ordinary HTTP request and the server keeps the response open, writing events into it whenever it likes:
GET /links/x7Kp/live
Accept: text/event-stream
← id: 1042
← data: {"clicks": 18431}
← id: 1043
← data: {"clicks": 18440}It's one-way, server to browser, and plain HTTP, so it passes through proxies and load balancers easily. Browsers reconnect automatically and send the last event ID they saw, so the server can fill in anything missed.
4. WebSockets
A WebSocket starts as an HTTP request that asks to upgrade the connection. After that, it's a long-lived, two-way channel: either side can send messages at any time, with very little overhead per message. It's the right tool when the browser also sends a stream of messages: chat, multiplayer editing, games.
| Polling | Long polling | SSE | WebSocket | |
|---|---|---|---|---|
| Direction | Client asks | Client asks, server waits | Server → client | Both ways |
| Delay | Up to the interval | Near instant | Near instant | Near instant |
| Wasted requests | Many | Few | None | None |
| Open connections | None between polls | One per waiting client | One per client | One per client |
| Works with plain HTTP tooling | Yes | Yes | Yes | Needs upgrade support |
| Good for | Slow-changing data | Fallback, older setups | Live feeds, notifications, counters | Chat, collaboration, games |
The hard part: holding connections at scale
Every SSE or WebSocket client is a connection that stays open for minutes or hours. That changes the design:
- Servers hold state again. Each connection lives on one particular server. When a click for
x7Kpis counted, the system has to find the server(s) holding connections that are watchingx7Kp. - So you add a fan-out layer. Connection servers subscribe to the topics their clients care about, usually through a pub/sub system, and the code that counts clicks publishes to the topic.
- Memory per connection adds up. Each open connection costs some memory and a file descriptor; a server can hold many thousands, but you size for connections, not just requests per second.
- Deploys and failures cause reconnect storms. Restart a server holding 50,000 connections and 50,000 clients reconnect at once. Clients should wait a random short delay before reconnecting (03.09 Timeouts, retries, backoff and jitter).
4 steps. Use Next and Back to follow the request.
Text version
- An owner opens the dashboard. The browser opens an SSE stream, and the load balancer pins it to one live server for as long as it stays open.
- That live server subscribes to the topic for x7Kp, if it isn't subscribed already.
- When the stats workers fold a batch of clicks into the count (02.06), they also publish the new count to the topic.
- Pub/sub delivers it to every live server subscribed to x7Kp, and each writes it into the streams of its watching dashboards.
- Dashboards → Load balancer
- Load balancer → Live servers
- Live servers → Pub/sub: subscribe
- Stats workers → Pub/sub: publish
Snip uses SSE: updates only flow one way, it's plain HTTP, and reconnection is built in. Polling every 10 seconds would also have been reasonable for a first version; the click counts already lag by a few seconds.
Seen in production
Slack
2023Real-time messaging
Every Slack client keeps a persistent WebSocket to Slack's servers. Clients connect to gateway servers in the nearest region, which track each user's channel subscriptions. Channel servers are stateful and in memory; each owns a subset of channels, assigned by consistent hashing. When a message arrives, its channel server sends it to every gateway subscribed to that channel, and Slack says messages are delivered across the world in about 500 ms.
What we foundPersistent WebSockets terminate on gateway servers; stateful channel servers, found by consistent hashing, fan messages out to them.
Real-time Messagingslack.engineering ↗You're adding live notifications ('your export is ready') to a web app. Users never send anything back over the channel. Which is the simplest good fit?
Recap
Interview questions
Compare polling, long polling, server-sent events and WebSockets. When would you use each?