WebSockets and real-time communication

HTTP is a client-asks-server-answers protocol, one request at a time. The moment a server needs to push data to a client without being asked first, HTTP's basic shape stops fitting — WebSockets are the fix.

Intermediate

4 min read

Why plain HTTP can't push data to a client

Every HTTP request/response cycle from the HTTP/APIs basics lesson follows the same shape: the client asks, the server answers, the exchange is done. This works fine for "load a page" or "submit a form," but it structurally cannot support the server initiating a message — a live chat app, a stock ticker, a multiplayer game's state updates, or a notification the instant something happens all need the server to send data without the client asking first, and plain HTTP has no mechanism for that at all.

The old workaround: polling

setInterval(() => {
  fetch("/api/messages/new").then(res => res.json()).then(showNewMessages);
}, 3000);

Polling — the client just asks "anything new?" repeatedly, every few seconds — is the simplest fix, and it's a real, still-commonly-used pattern for things that don't need true instant delivery. Its cost is real too: most requests return "nothing new," wasting a full HTTP request/response cycle (including connection setup) for no actual data, and there's an unavoidable delay between something happening and the next poll picking it up — averaging half the poll interval, in the best case.

WebSockets: one connection, open in both directions

const socket = new WebSocket("wss://example.com/chat");
 
socket.onmessage = (event) => {
  console.log("received:", event.data);   // the server can send this anytime, unprompted
};
 
socket.send("hello from the client");

A WebSocket starts as a regular HTTP request but then upgrades to a persistent, bidirectional connection — both the client and the server can send messages on it at any time, in either direction, without a new request/response cycle for every message. Once established, the connection just stays open; the server can push a new chat message the instant it exists, with no polling delay and no wasted "nothing new" requests. This is the actual mechanism behind live chat apps, real-time dashboards, and multiplayer games needing to feel instant rather than a few seconds behind.

The handshake: starting as HTTP, then switching protocols

A WebSocket connection begins as a normal HTTP request carrying special Upgrade headers — this is deliberate, since it lets a WebSocket connection use the same port (443 for wss://, the encrypted version) and pass through the same infrastructure (proxies, load balancers) that already understands HTTP. The server responds 101 Switching Protocols to accept the upgrade, and from that point on the same underlying TCP connection carries WebSocket frames instead of HTTP requests — one connection, reused for the entire real-time session, instead of a new connection per message.

The real cost: WebSockets break the stateless-server assumption

Client <-----------------------------> Server Instance #3
(this specific connection is pinned to this specific server process)

The scaling-basics lesson's horizontal scaling story assumes any request can land on any server, because HTTP requests are independent and stateless. A WebSocket connection is the opposite: it's a long-lived, stateful connection tied to one specific server process for its entire duration — a load balancer can route the initial handshake anywhere, but that connection then has to stay pinned to whichever server accepted it. This creates a genuine version of the sticky-session problem from the load balancing lesson, and it means broadcasting a message to "everyone in this chat room" requires coordinating across every server instance that might be holding a relevant connection, not just querying one database.

The distributed-fanout problem this creates

If a message needs to reach every participant in a chat room, and those participants' WebSocket connections are spread across multiple server instances, one server can't just loop over "its" connections and call it done — it has to somehow notify the other servers holding the remaining connections too. This is exactly the message-queues (or a pub/sub system like Redis pub/sub) lesson's pattern applied to real-time delivery: publish the message to a shared channel, and every server subscribed to that channel forwards it to whichever of its own local connections need it.

When WebSockets are worth the added complexity, and when they aren't

Polling is simpler to build, deploy, and scale (it's just ordinary stateless HTTP), and is genuinely fine when a few seconds of delay is acceptable — a dashboard that refreshes stock prices every 5 seconds doesn't need WebSockets. WebSockets earn their real complexity cost (sticky connections, cross-server fanout, a separate protocol to reason about) specifically when the delay itself is the problem: live chat, collaborative editing, multiplayer games, or anything where a user would visibly notice a multi-second lag.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. Why can't plain HTTP support a server sending data to a client without the client asking first?

2. What's the real cost of polling compared to WebSockets?

3. Why does a WebSocket connection begin as a regular HTTP request with Upgrade headers, rather than a completely separate protocol from the start?

4. Why does broadcasting a chat message to everyone in a room require more than one server querying a database, once WebSockets are involved?