The Options Ladder
four ways to move a server-side event to a clientThe web is pull-based: clients ask, servers answer. Real-time delivery is the set of tricks for reversing that, so the server can push the moment something changes. There are four rungs, each one closer to a true push channel and each one more stateful than the last.
SHORT POLLING client asks every N seconds, mostly gets "nothing yet"
C โreqโโถ S (empty) โ simple, works everywhere
C โreqโโถ S (empty) โผ wasteful; latency = poll interval
C โreqโโถ S (data!) โ
LONG POLLING client asks, server HOLDS the request until data exists
C โreqโโโโโโโโโโถ S โ near-real-time on plain HTTP
(waits) โผ one connection per message; reconnect churn
C โโโdataโโ S reopen โ
SERVER-SENT EVENTS one long-lived HTTP stream, server pushes many events
C โreqโโถ S โ server โ client only, auto-reconnect built in
C โโeventโ S โผ text only, rides plain HTTP
C โโeventโ S โ
WEBSOCKETS upgrade HTTP once, then a full-duplex TCP channel
C โ handshake โ S โ bidirectional, low overhead per message
C โโโโโโโโโโโโ S โผ stateful, needs its own scaling story
(both push anytime) โ
Choosing Between Them
direction, frequency, and how much complexity you can affordReach for the simplest rung that meets the requirement. Three questions settle it: which direction does data flow, how often does it update, and how much connection machinery are you willing to run.
| Technique | Direction | Latency | Cost / complexity | Best fit |
|---|---|---|---|---|
| Short polling | Client pull | = poll interval | Trivial, but wasteful | Rare updates, small scale, quick hacks |
| Long polling | Server โ client | Near real-time | Reconnect churn | Notifications where WebSockets are overkill |
| SSE | Server โ client | Real-time | Low, auto-reconnect | Feeds, live scores, dashboards, notifications |
| WebSockets | Bidirectional | Real-time | High, stateful | Chat, games, collaborative editing, trading |
Connection State Is the Hard Part
persistent connections make servers statefulA stateless HTTP server is easy: any box handles any request. A persistent connection breaks that. The client is now bound to one specific server for the life of the connection, and the system has to answer questions it never had to before.
To push to Bob, you must find the exact server holding Bob's socket. Two options: sticky routing (a load balancer pins Bob to one node) or a connection registry (a shared store mapping user โ server, so any node can look up where to send).
TCP can stay "open" long after the client vanished (laptop closed, tunnel dropped). Heartbeats โ periodic pings โ detect the dead ones so you free memory and stop pushing into the void.
Connections drop constantly โ phones change networks, servers deploy. The client reconnects with exponential backoff and jitter so a mass disconnect doesn't become a synchronized reconnect stampede (Resilience Patterns). On reconnect it sends a last-event-id so the server can replay whatever was missed during the gap, rather than losing it.
Scaling to a Million Connections
a dedicated tier and a pub/sub backboneEach open connection costs memory (buffers, TLS state โ order of tens of KB), so a million connections is real RAM and file-descriptor pressure. The standard shape separates the connection-holding tier from everything else, and uses a pub/sub backbone to fan messages out to whichever gateway holds the target.
millions of clients
โ โ โ (WebSocket / SSE)
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Connection gateways: a dedicated tier
โ GW-1 GW-2 GW-3 ... โ whose only job is holding sockets.
โโโโโฌโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโ Scale this independently of app logic.
โ โ โ
โโโโโโโโโโโผโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโ Pub/sub backbone (Redis / Kafka):
โ pub / sub โ a message for user X is published;
โ backbone โ the gateway holding X is subscribed
โโโโโโโโโฌโโโโโโโโ and delivers it. No gateway-to-gateway
โผ mesh needed.
โโโโโโโโโโโโโโโโโ
โ app services โ Business logic stays stateless behind
โโโโโโโโโโโโโโโโโ the gateway tier.
The gateways become stateless in the way that counts: they hold connections but no business logic, so you can add nodes freely. On deploy, you drain a gateway โ stop new connections, let clients reconnect elsewhere with backoff โ instead of dropping everything at once (Scaling Patterns).
Delivery Over Flaky Links
acks, buffers, and per-channel orderA pushed message can vanish: the connection dropped between send and receive, and the server never knew. "I sent it" is not "they got it." Guaranteeing delivery over an unreliable link needs three pieces.
| Piece | What it does |
|---|---|
| Client acks | The client confirms receipt; unacked messages are considered undelivered and eligible for resend. |
| Server-side buffer | Hold recent messages per client until acked, so a reconnecting client can be replayed from where it left off. |
| Per-channel ordering | Sequence numbers within a conversation or topic, so the client can order and dedup even if delivery is out of order. |
Quick Reference
the four techniques on one card| Technique | Latency | Direction | Cost | Best fit |
|---|---|---|---|---|
| Short polling | = interval | Client pull | Trivial, wasteful | Rare updates, small scale |
| Long polling | Near real-time | Server โ client | Reconnect churn | Simple notifications |
| SSE | Real-time | Server โ client | Low, auto-reconnect | Feeds, dashboards, live scores |
| WebSockets | Real-time | Bidirectional | High, stateful | Chat, games, collaboration |