Handbooks  /  WebSockets & Real-Time
Handbook~15 min readNetworkingworked math + runnable code
The WebSockets & Real-Time Handbook

Don't ask.
Be told.

HTTP has one rhythm: the client asks, the server answers, the line goes dead. That's fine for loading a page — and hopeless for anything live. To show a new chat message or a moving cursor, the old trick is to poll: ask "anything new?" over and over, mostly to hear "no." It's laggy and wasteful, and the math says exactly how much. WebSockets flip it: keep one line open, and let the server push the instant something happens. This handbook is the arithmetic of why "be told" beats "keep asking."

01

HTTP can't push

Ordinary HTTP is request-response: the client initiates, the server replies, and the exchange is over. The server has no way to speak first — it can't tap the client on the shoulder and say "something changed." That's a perfect fit for fetching a document, and a fundamental mismatch for anything that updates on the server's schedule: a message arriving, a price ticking, another user's cursor moving.

So to build "live" on top of a protocol that can't push, developers reach for a workaround — have the client ask, repeatedly, and hope to catch news soon after it happens. That's polling, and it works, sort of. But it's a simulation of real-time built out of not-real-time parts, and it pays for the illusion in latency and wasted work. Quantifying that cost is the cleanest way to see why a different primitive — a connection the server can push over — is the real answer.

The one-sentence version

Polling makes the client wait on average half the poll interval and waste a request whenever nothing changed; a WebSocket keeps a line open so the server pushes instantly, at one-way network latency and zero waste.

02

The cost of polling

Polling means the client asks "anything new?" every fixed interval. It has two costs, and both are quantifiable. The first is latency: an event can happen at any moment within the interval, so on average the client doesn't learn about it until half the interval has passed. Poll every 10 seconds and your users feel about 5 seconds of staleness on average — an eternity for a chat.

The second is waste. Most polls return nothing — you asked, there was no news — so you're making a stream of requests just to hear "no," burning bandwidth, server CPU, and battery. And the two costs are in tension: to cut the latency you shorten the interval, but that multiplies the number of empty polls. Poll every second instead of every ten and you've traded 5 seconds of lag for ten times the wasted requests. There's no setting of the dial that gives you both low latency and low waste, because you're fundamentally guessing when to ask. The fix isn't a better interval — it's not guessing at all.

03

A line left open

A WebSocket changes the primitive. After a quick HTTP handshake that "upgrades" the connection, the client and server hold open a single, persistent, full-duplex TCP connection — either side can send a message at any time, for as long as the connection lives. Now the server doesn't wait to be asked; it pushes the instant an event occurs.

Poll vs push
PollClient asks every N seconds → learns of an event after ~N/2; most asks return nothing.
Long-pollServer holds the request open until there's news — better, but one event per request.
WebSocketOne open line; server pushes instantly → latency ≈ one-way network time, zero empty requests.
SSEOne-way server→client push (simpler) when you don't need client→server.

Both costs of polling vanish. Latency drops from "half a poll interval" to "one network hop" — tens of milliseconds, effectively the moment it happens. And there are no wasted requests: nothing crosses the wire until there's actually something to send. That's the whole appeal — a WebSocket turns "check constantly, usually find nothing" into "be notified precisely when it matters." The next section makes the difference exact.

04

The latency math

Polling with interval T: an event is equally likely anywhere in the window, so the expected wait to learn of it is half the interval, and the requests that return nothing are all the polls minus the ones that caught an event:

poll latency  =  T / 2     wasted requests  =  (duration / T) − events

T=10s → 5s average staleness; over 100s that's 10 polls for maybe 2 events → 8 empty requests. Shrink T and latency falls but waste climbs.

A WebSocket push arrives at roughly the one-way network latency, independent of any interval, with no empty requests — so it clears a real-time perceptual threshold that polling can't:

push latency  ≈  network  (≪ T/2)   ⟹   feels real-time when latency ≤ threshold (push yes, slow-poll no)

50 ms push clears a 500 ms "feels live" bar; a 10 s poll's 5 s average doesn't. The runnable version below computes poll latency, wasted requests, and the push comparison.

RUN IT YOURSELF

Poll vs push, in numbers

The push-vs-poll difference is arithmetic. Polling's average latency is half the interval, because an event lands uniformly in the window — so a 10-second poll means ~5 seconds of staleness. It also wastes a request every time nothing changed: total polls minus real events. A WebSocket push arrives at the one-way network time regardless of interval, and sends nothing when there's no news — so it clears a real-time threshold a slow poll can't. Shorter polls cut latency but multiply the waste, the trade you can't win. Change the interval, event count, or network time and compare.

CPython · WebAssembly
05

When to use what

Real-time isn't free, so match the transport to the need:

TransportBest for
Request-responseOne-off fetches; data that only changes when the user acts. Simplest, no persistent state.
PollingInfrequent updates where a few seconds of staleness is fine and simplicity matters.
Server-Sent EventsFrequent server→client push, one-directional (live feeds, token streaming) — simpler than WebSockets.
WebSocketLow-latency, bidirectional, frequent messaging: chat, collaboration, live cursors, games.
WebRTCPeer-to-peer, ultra-low-latency media (audio/video) — a different tool for a different job.

The honest default is: don't reach for WebSockets unless you need them. A persistent connection costs server resources per client, complicates load balancing (a socket is stateful; a stateless request isn't), and needs reconnection and heartbeat machinery. For streaming from the server only — an LLM's tokens, a live log — SSE is lighter and often enough. Save WebSockets for genuinely interactive, bidirectional, low-latency features, where the poll math is unacceptable and the two-way channel earns its keep.

06

Pitfalls

The operational reality is that a persistent connection is stateful, and state is what makes scaling hard. Each open socket pins a bit of memory and a connection slot, so a million concurrent users is a million live connections to hold; load balancers must keep a client stuck to the server that holds its socket (or you need a shared pub/sub layer to fan out messages across servers). Plan for that from the start, or your real-time feature falls over at scale. And connections drop — networks are flaky, phones sleep — so you need automatic reconnection, heartbeats/pings to detect dead connections, and a way to catch up on messages missed while disconnected.

Two more. Reaching for WebSockets when SSE or polling would do: bidirectional, low-latency is powerful but heavy, and using it for infrequent one-way updates is over-engineering. And treating socket messages as trusted: a WebSocket is an open door into your server, so authenticate the connection, authorize every message, and rate-limit — the same discipline as any endpoint. Used where it fits, a WebSocket is the difference between an app that feels alive and one that feels like it's refreshing behind your back. The rule is simple: when staleness is unacceptable and updates are frequent and two-way, stop asking — open a line and be told.

Worth knowing

A WebSocket is stateful, which is the whole scaling challenge: each client pins a connection, so you need sticky routing or a shared pub/sub to fan messages across servers, plus reconnection, heartbeats, and missed-message recovery. Don't adopt it for infrequent one-way updates SSE or polling would handle more simply.

Frequently asked

Quick answers

What is a WebSocket?

A persistent, full-duplex connection where either side can send at any time, so the server can push updates instantly — the basis of real-time features.

Why is polling inefficient?

You wait on average half the poll interval to learn of an event, and most polls return nothing — so it's both laggy and wasteful.

How do WebSockets cut latency?

The line is already open and the server pushes, so latency is one network hop (tens of ms) instead of half a poll interval, with no empty requests.

When not to use them?

When updates are infrequent or one-way — request-response, polling, or SSE are simpler and cheaper than a stateful persistent connection.

Finished this one? 0 / 99 Handbooks done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.