Notes
Polling
The simplest approach is polling: ask every few seconds, 'anything new?' It works, but with a million users that's hundreds of thousands of requests a second, almost all answered 'nothing', and a message can still wait seconds to show up. Fine for a dashboard that refreshes. Bad for chat.
- every 3 s: GET /messages?since=…
- 1M users × 1 request / 3 s
- = 330,000 requests per second
- almost all answer "nothing"
- and a message can wait
- up to 3 s to appear
- fine for: dashboards, slow data
Long polling
Long polling is smarter. The client asks, and the server simply doesn't answer until there's something to say, or about thirty seconds pass. When the answer arrives, the client asks again right away. Delivery becomes nearly instant using plain HTTP, but every message still costs a full request.
- client asks, server does NOT
- answer until data arrives
- (or a timeout, ~30 s)
- client immediately asks again
- + near-instant delivery
- + plain HTTP, works everywhere
- − a request per message
- − holding requests costs memory
SSE
Server-sent events keep one HTTP response open, and the server keeps writing events into it: score one nil, score two nil. It's one direction only, server to client, it's plain HTTP, and browsers reconnect automatically. Perfect for live scores, notifications and streaming text.
GET /live (Accept: text/event-stream)
data: {"score":"1-0"}
data: {"score":"2-0"}
server → client only
plain HTTP, auto-reconnect
good for: feeds, live scores,
notifications, AI token streams WebSockets
WebSockets go further. The connection starts as HTTP, then upgrades into a persistent two-way channel. Both sides can send whenever they like, with tiny frames and no headers per message. This is what chat, multiplayer games and collaborative editors use. The cost: each connection is long-lived state the server must hold.
- starts as HTTP, then "Upgrade"
- to a persistent TCP connection
- both sides send anytime
- tiny frames, no headers each time
- good for: chat, multiplayer games,
- collaborative editing, trading
- cost: stateful, long-lived
- connections to manage
Routing
With many chat servers, Alice may be connected to server one and Bob to server two. So each server records who is connected where, often in Redis: Bob is on server two. To deliver, server one looks Bob up and forwards the message, directly or through a pub-sub channel. If Bob is offline, store it and send a push notification.
on connect: SET conn:bob = server-2 to deliver: look up conn:bob forward to server-2 (or pub-sub channel per server) offline? store the message, send a push notification
Scaling
A single server with an event loop can hold hundreds of thousands of mostly idle connections. The load balancer has to support long-lived connections, heartbeats detect dead ones, and when a server restarts, clients reconnect with backoff and resume from the last message id they saw, so nothing is lost.
- one server: ~100k–1M idle
- sockets with an event loop
- load balancer must support
- long-lived connections
- deploys drop connections:
- clients reconnect with backoff
- and resume from last seen id
- heartbeats detect dead ones
Decision
How to choose? Data that changes every minute: just poll. A one-way stream like notifications or a live feed: server-sent events. Two-way and low latency, like chat, games or collaborative editing: WebSockets. And when the app is in the background, none of these work, so you use mobile push notifications.
- updates every minute → polling
- server → client stream,
- notifications, feeds → SSE
- two-way, low latency,
- chat, games, editors → WebSockets
- need it to work anywhere,
- simple infra → long polling
- mobile apps in background:
- push notifications (APNs/FCM)
Say it like this
The interview answer for chat. Each online client holds a WebSocket to a gateway server. A registry maps each user to their gateway. Save the message first, then route it through pub-sub to the right gateway. Offline users get a push notification, and reconnecting clients resume from the last message id.
- "How does Bob get it instantly?"
- WebSocket per online client
- gateway servers hold sockets
- registry: user → gateway
- route via pub-sub
- store message first, then deliver
- offline → push notification
- reconnect + resume from last id