Patterns

Real-time Updates

Getting new data to the client the moment it changes — polling, long polling, Server-Sent Events and WebSockets, and how to choose based on direction, frequency and scale.


Lots of features need the screen to update without the user hitting refresh: a chat message arrives, a ride is accepted, a bid is outbid, a dashboard ticks up. The pattern question is always the same — how does the server get fresh data to a client that already loaded the page? There are four answers, and the right one falls out of two questions: which direction do messages flow, and how often?

The four options

Polling. The client asks again on a timer (GET /messages?since=... every few seconds). Crude, but dead simple to build and operate, and it works through every proxy and load balancer. Perfectly fine when updates can lag a few seconds and the data isn't huge.

Long polling. The client makes a request and the server holds it open until there's something to send (or a timeout), then the client immediately reconnects. Near-real-time with ordinary HTTP and no persistent protocol — a good fallback when WebSockets aren't available.

Server-Sent Events (SSE). One long-lived HTTP connection the server streams down. One-directional (server → client), text-based, with automatic reconnection built in. Ideal for feeds, notifications, live counters, and streaming an AI response token by token.

WebSockets. A single persistent, two-way connection. Both sides push messages at any time. The right tool when the client and server both talk continuously: chat, multiplayer games, collaborative editing, live cursors.

Choosing

NeedUse
Occasional updates, simplest opsPolling
Near-real-time, plain HTTP onlyLong polling
Server → client stream (feeds, AI output, notifications)SSE
Two-way, high-frequency (chat, games, docs)WebSockets

The instinct to resist is reaching for WebSockets by default. If the data only flows one way, SSE is simpler. If a few seconds of lag is fine, polling is simpler still. Only pay for a two-way persistent connection when the interaction genuinely needs one.

The real cost: connection state

Polling is stateless — any server can answer any request, so it scales like a normal API. Persistent connections (SSE, WebSockets) are stateful: every open connection is memory and a file descriptor pinned to one server. That changes your architecture:

  • You need enough servers to hold all concurrent connections (a chat app with 1M online users is holding 1M sockets somewhere).
  • A message for user B might arrive at a server holding user A. You need a pub/sub layer (e.g. Redis) so any server can broadcast to the connection wherever it lives.
  • Load balancers must support long-lived connections, and you must drain them gracefully on deploy.

The fan-out question

When one event must reach many clients (a popular streamer goes live; a message hits a 500-person group), decide where the fan-out happens. Writing the message once and pushing it to thousands of connections through a pub/sub topic keeps the write cheap; materialising it into thousands of inboxes is more work but makes reads trivial. Which you pick depends on the read/write ratio — the classic feed trade-off.

What to say in an interview

Pick from direction and frequency, and name the scaling cost:

"Chat is two-way and high-frequency, so WebSockets. Each server holds a slice of the connections; when a message comes in I publish it to a Redis channel so whichever server holds the recipient's socket can push it down. For the 'your ride was accepted' notification I'd use SSE instead — it's one-directional, so there's no need for a full duplex socket."


Next up: Dealing with Contention — when many clients fight over the same row.