Real-Time Transports

Medium

The problem

A page that shows live data (chat, notifications, prices, presence) needs the update the moment the server has it. Plain HTTP is request then response: the browser asks, the server answers, the connection closes. The server has no way to speak first. Three techniques work around that, and each costs something different.

See all three at once

The same chat runs over long-polling, SSE, and WebSocket at the same time. Press "Friend sends" to push an incoming message and watch which column shows it first. Press "You send" to see how each transport sends back. The request counter under each column is the running cost: how many HTTP requests that approach has spent.

SYSTEM DESIGN · REAL-TIMEThree Ways to Push a Message
live
Overview
Long-pollingHTTP
Ask, wait, repeat. The server holds each request open until it has news.📞 Like phoning the shop over and over: “Ready yet?” They answer, you hang up, then call again.
CLIENTbrowser
POST
SERVERapi
1 HTTP request↕ one at a time
hey 👋
hi!
SSEEventSource
One open stream. The server pushes events down it; the client just listens.📻 Like a radio station — it keeps broadcasting to you, but you can’t talk back on it.
CLIENTbrowser
POST
SERVERapi
1 HTTP request↓ one-way
hey 👋
hi!
WebSocketws://
One upgraded connection both sides can write to, anytime.☎️ Like a phone call left open — either person can speak the instant they want.
CLIENTbrowser
SERVERapi
1 HTTP request⇅ two-way
hey 👋
hi!
One chat, three transportsEvery panel runs the same conversation, but each keeps the browser in sync a different way. Press ↓ Friend sends and watch where the message appears first — and how much traffic each approach costs to get it there.
Long-pollSSEWebSocketDirection↕ one req at a time↓ server → client⇅ full-duplexConnectionnew request each cycle1 persistent (HTTP)1 persistent (TCP)Client → serverseparate POSTseparate POSTsame socketOverheadhigh · headers × NlowlowestReconnectalways reconnectingautomaticdo it yourselfBest forlegacy / fallbackfeeds, notificationschat, games, collab

Long-polling

The client makes a request and the server holds it open until it has something to send (or a timeout fires). The response closes the connection, so the client opens another one right away. Every cycle re-sends the full set of HTTP headers, so the request counter keeps climbing even when nothing arrives. An incoming message also has to wait for the next poll to open, which is why long-polling is the slowest of the three to deliver.

It asks nothing special of the browser or the network, so it is the option everything else falls back to.

Server-Sent Events

One request opens a stream that the server keeps open, then writes events down it as plain text. Delivery is instant and the browser reconnects on its own if the stream drops. (There is a dedicated walkthrough in Server Sent Events.)

The stream runs one direction only, server to client. To send a message the client opens a separate POST on its own request, the same way long-polling does. This is the part people miss: SSE does not make the page two-way. In the widget that POST travels a different lane from the green stream, and it bumps the SSE request counter by one.

WebSocket

One HTTP request upgrades to a persistent TCP socket that either side can write to at any time. A send rides the same socket as the messages coming in, so there is no extra request and the counter stays at one. It has the lowest overhead and the lowest latency, which is why chat, multiplayer games, and collaborative editing use it.

The trade is that more is left to you. There is no built-in reconnect and no message framing beyond the raw socket, so heartbeats and retries are your code to write.

When to reach for which

  1. Server to client only (notifications, feeds, dashboards): SSE is the least code.
  2. Both directions with the tightest latency (chat, multiplayer, live cursors): WebSocket.
  3. An old proxy or browser that supports neither: long-polling is the fallback everything degrades to.