Skip to content
RungsySign in

WebSockets

A connection that stays open both ways — real-time, at the cost of statefulness.

40 minDifficulty 3/5realtime · protocolAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.

Before this

Why this exists

A chat app polling `GET /messages` every two seconds means every user waits up to two seconds to see a new message, and the server handles constant requests that mostly return "nothing new" — wasted round trips for the common case. A WebSocket keeps one connection open in both directions, so the server can push a new message the INSTANT it exists, with zero polling and zero wasted requests.

The mental model

An HTTP request is a phone call where you ask a question, get an answer, and hang up — every new question needs a whole new call. A WebSocket is leaving the phone line open: either side can say something at any moment, without redialing, for as long as the connection stays up.

How it works

The connection starts as HTTP, then upgrades

A WebSocket connection begins as a normal HTTP request with an `Upgrade: websocket` header — the server responds with a `101 Switching Protocols` status, and from that point on, the SAME underlying TCP connection is repurposed to carry WebSocket frames instead of further HTTP requests. This is why WebSockets can traverse the same infrastructure (proxies, load balancers) that HTTP already works through.

Either side can send a message at any time, unprompted

Unlike HTTP's strict request-then-response pattern, a WebSocket connection has no such constraint — the server can send data to the client with no preceding request from that client, which is exactly what makes server-initiated push (a new chat message, a live price update) possible without the client ever having to ask.

The connection is stateful, which is the whole point and the whole cost

A WebSocket connection is held open on a SPECIFIC server for its entire duration — the server maintains state (which connection belongs to which user, what they're subscribed to) for as long as it's open. This directly conflicts with the stateless, horizontally-scalable request model most APIs use, and is why scaling WebSockets across multiple server instances requires extra infrastructure (like a shared pub/sub layer) to route messages to whichever specific server holds a given user's connection.

Reconnection and message ordering aren't automatic — the app has to handle them

A WebSocket connection can drop (a network blip, a server restart) without warning, and reconnecting means the client has potentially missed messages sent while disconnected — the protocol itself doesn't buffer or replay anything. A robust implementation needs its own reconnection logic and often a way to catch up on missed state (like fetching recent messages via a regular API call after reconnecting) rather than assuming the WebSocket alone is a reliable delivery mechanism.

The mechanism

The handshake happens once, as a normal HTTP exchange that upgrades the connection. After that, the same TCP connection carries lightweight WebSocket frames in either direction, with no per-message HTTP overhead (headers, a new connection) and no requirement that a message from the server be preceded by a client request — it stays open until either side closes it or the connection drops.

sequenceDiagram
  participant C as Client
  participant S as Server
  C->>S: HTTP GET, Upgrade: websocket
  S->>C: 101 Switching Protocols
  Note over C,S: Connection now carries WebSocket frames
  C->>S: message (client-initiated)
  S->>C: message (server-initiated, unprompted)
  S->>C: message (server-initiated, unprompted)
Diagram source for WebSockets.

What people get wrong

WebSockets should be used for all real-time-feeling features by default.
The stateful, held-open connection has real infrastructure cost (server memory per connection, scaling complexity) that server-sent events or even short-interval polling can avoid for simpler, one-directional use cases like a live notification feed. Reaching for WebSockets everywhere adds operational complexity (connection management, scaling with pub/sub) for use cases that a simpler, unidirectional mechanism would have handled just as well.
A WebSocket connection guarantees message delivery, like a reliable queue.
If the connection drops, any message sent during the disconnection is simply lost — there's no built-in buffering or replay, so an application needing guaranteed delivery has to build that on top, typically by re-fetching missed state after reconnecting. Assuming WebSockets are inherently reliable leads to silently missed messages during network blips, which is exactly the kind of intermittent bug that's hard to reproduce and easy to miss in testing.
Scaling a WebSocket server is the same as scaling a regular stateless HTTP API — just add more instances behind a load balancer.
Because each connection is held open on one specific server instance, simply adding more instances doesn't automatically let them share state — routing a message to a specific user requires knowing WHICH instance holds their connection, typically solved with a shared pub/sub layer (like Redis) that all instances subscribe to. This is a genuine architectural difference from stateless HTTP scaling, and teams that don't plan for it discover the problem only once they actually need more than one server instance.

When not to use it

The server needs to push updates to the client, but the client never needs to send anything back over the same channel.
Server-Sent Events (SSE), a simpler, unidirectional protocol built on plain HTTP, avoiding WebSocket's added complexity when bidirectionality isn't actually needed.
Updates happen infrequently (every few minutes) and don't need to be instant.
Simple polling at a reasonable interval — the operational simplicity of stateless HTTP requests may outweigh the latency cost for genuinely infrequent updates.

Terms

Upgrade handshake
The initial HTTP request/response exchange that switches a connection from plain HTTP to the WebSocket protocol.
Frame
The basic unit of data sent over an established WebSocket connection, lighter-weight than a full HTTP request.
Server-initiated push
The server sending data to the client with no preceding request from that client, possible over WebSockets but not standard HTTP.
Sticky session / pub-sub layer
Infrastructure needed to route messages to the correct server instance holding a specific user's WebSocket connection, when scaling across multiple instances.

In an interview

Why is scaling a WebSocket-based chat server across multiple instances harder than scaling a stateless REST API?

  • each WebSocket connection is held open on one specific server instance, which maintains state about it
  • a message meant for a user connected to instance A can't just be handled by instance B — B doesn't have that connection
  • solving this typically requires a shared pub/sub layer (e.g. Redis) that all instances subscribe to, to route messages to whichever instance actually holds the target connection

Can you recall it?

Why does a WebSocket connection allow the server to send a message to the client without the client having requested anything, when a regular HTTP API cannot?

Keep track of this

Add Asynchronous Integration to your map and Rungsy will schedule reviews so you actually remember it.