21. Polling vs WebSockets vs Push
21.2 What are the options?
At the gate: Five ways to wait for the groceries, from the most annoying (keep calling) to the most convenient (a note when you're out).
In the system: Here's the same "packed" update arriving four different ways:
Figure: polling finds out late and mostly gets empty answers; the others hear the moment it happens.
21.2.1 Short polling
At the gate: Call every 5 seconds. "Any update?" "No." You hear about "packed" up to 5 seconds late, and almost every call is a waste.
In the system: The client sends a normal request on a timer:
In the chapter's code, one 20-minute order polled every 5 seconds makes 240 requests, and 235 of them come back with nothing new (98% wasted). Updates arrive on average about 2.8 seconds late (about half the interval). Poll every 30 seconds instead: only 40 requests, but updates arrive about 17 seconds late.
- Good: dead simple, works everywhere, easy to cache and scale like any HTTP request.
- Bad: a trade-off you can't escape: faster updates mean more wasted requests. At 100,000 users polling every 5 seconds, that's 20,000 requests a second, nearly all empty.
- Fine for: slow-changing things ("is my report ready?"), small user counts, or as a fallback.
Memory code: "Ask often and waste, or ask rarely and wait."
21.2.2 Long polling
At the gate: You call and say "don't hang up until there's news". The courier holds the line. When "packed" happens, they tell you at once and hang up; you call straight back. If nothing happens for 30 seconds, they say "nothing yet" and you call again.
In the system: The client sends a request; the server holds it open until it has something to send or a timeout (say 30 seconds) passes. Then the client immediately sends the next request.
- Good: updates arrive almost instantly, using plain HTTP. In the chapter's code, the same order needs 45 requests instead of 240, with no delay.
- Bad: every held request ties up a connection on the server. There's a gap between one response and the next request where an update can slip through (the client must ask "anything since event 3?" to cover it). Timeouts in proxies and load balancers have to be longer than the hold time.
- Fine for: a fallback when WebSockets or SSE are blocked; older systems.
Memory code: "Stay on the line until there's news, then call straight back."
21.2.3 Server-sent events (SSE)
At the gate: Switch on the shop's radio station. It announces every update as it happens. You only listen; to say something back, you'd have to phone separately.
In the system: SSE is a standard way to stream events from server to client over one long-lived HTTP response. In the browser, it's the EventSource API:
The server keeps the response open and writes events in a simple text format:
id: 3
event: status
data: rider assigned
- Good: one-way, server to client, which is all most "live" features need. Plain HTTP, so it works with normal load balancers, auth and HTTP/2. The browser reconnects automatically and sends the last event ID it saw in a
Last-Event-IDheader, so the server can resend anything missed (21.4). - Bad: text only, one direction. Over HTTP/1.1, browsers allow only about 6 connections per domain, shared by all tabs, so several open tabs can starve each other; with HTTP/2, many streams share one connection and this goes away.
- Fine for: order tracking, live scores, notifications in a web app, dashboards, and streaming AI responses token by token.
Memory code: "Switch on the radio: the server talks, you listen."
21.2.4 WebSockets
At the gate: An open phone line. Either of you can speak at any time, instantly, for as long as the line stays up.
In the system: A WebSocket starts as an HTTP request with an Upgrade: websocket header; if the server agrees, the same TCP connection becomes a two-way channel for messages in both directions (defined in RFC 6455).
- Good: both directions, very low overhead per message (no HTTP headers each time), text or binary.
- Bad: it's not HTTP after the handshake, so you handle auth, reconnects, catching up, heartbeats and routing yourself. Each connection is stateful: it lives on one server, which complicates deploys and load balancing (21.3). Some corporate proxies block it.
- Fine for: chat, multiplayer games, collaborative editing, live trading screens: anything where the client also talks often and fast.
Memory code: "An open phone line: either side can talk, anytime."
21.2.5 Mobile push (APNs, FCM)
At the gate: You've gone out. The shop can't call you, but it can leave a note in the letterbox, and you'll see it when you pass by. Short notes only.
In the system: When the app is closed or in the background, phones don't keep your app's connections alive (to save battery). Instead, the phone keeps one connection to the platform's push service, and every app's notifications come through it:
- APNs (Apple Push Notification service) for iPhones.
- FCM (Firebase Cloud Messaging) for Android (and it can relay to iOS too).
Your server sends the notification to APNs or FCM with the device's token; they deliver it to the phone.
- Good: reaches users when the app isn't running. The only option for "your order is out for delivery" while the phone is in a pocket.
- Bad: small payloads (4 KB on both APNs and FCM), no delivery guarantee or ordering, possible delays, and users can turn notifications off. Device tokens change and must be kept up to date.
- Rule: treat a push as a nudge, not the data itself. "Your order is out for delivery" opens the app, which then fetches the real state from the server. Chapter 33 designs a full notification system.
Memory code: "A note in the letterbox: short, maybe late, but it reaches you when you're out."