WebSockets
In a Nutshell
WebSockets provide a persistent, two-way (full-duplex) communication channel between a client and a server over a single long-lived connection. Ordinary HTTP is request-response — the client asks, the server answers, and the connection is effectively one-directional and short-lived. That's a poor fit for anything real-time: chat, live sports scores, collaborative editing, trading dashboards, multiplayer games. WebSockets solve this by upgrading an HTTP connection into a durable pipe where either side can send a message at any time, with almost no per-message overhead. When you need the server to push data to clients instantly, WebSockets are the go-to.

How It Actually Works
From HTTP to a Persistent Socket
A WebSocket connection begins as a normal HTTP request with an Upgrade header, then switches protocols on the same TCP connection:
Client → Server (HTTP upgrade request):
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Server → Client (agrees to switch):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
── From here the TCP connection carries WebSocket frames both ways ──
── ws:// (plaintext) or wss:// (over TLS, the standard) ──
After the 101 handshake, there's no more HTTP overhead — just lightweight message frames flowing in both directions until either side closes.
The Real-Time Communication Options Compared
WebSockets aren't the only way to get server-to-client updates. Know the trade-offs:
| Technique | Direction | Connection | Best For |
|---|---|---|---|
| Short polling | Client pulls repeatedly | New request each time | Simple, infrequent updates |
| Long polling | Client pulls, server holds until data | Held then re-opened | Near-real-time without WebSockets |
| Server-Sent Events (SSE) | Server → client only | One long-lived HTTP | Server push (feeds, notifications) |
| WebSockets | Full-duplex both ways | One persistent socket | Chat, gaming, collaboration |
| WebRTC | Peer-to-peer | Direct (UDP) | Low-latency audio/video/data |
Polling: client: "anything new?" ... "anything new?" ... (wasteful)
Long poll: client: "anything new?" → server waits → replies when it is
SSE: server ───stream of events──▶ client (one direction)
WebSocket: client ◀════full-duplex════▶ server (both directions)
Decision heuristic: need only server→client push? SSE is simpler and auto-reconnects over plain HTTP. Need genuine two-way, low-latency messaging? WebSockets. Need media/peer-to-peer? WebRTC.
The Hard Part: Scaling Stateful Connections
WebSockets are stateful — each connection lives on a specific server for its whole lifetime. This breaks the easy statelessness that makes HTTP horizontally scalable, and creates real challenges:
| Challenge | Why It's Hard | Solution |
|---|---|---|
| Load balancing | Connections are long-lived and sticky | L4 LB or sticky sessions; connection-aware routing |
| Broadcasting | A message for user X may need a server that doesn't hold X's socket | A pub/sub backplane (Redis, Kafka) fans messages across servers |
| Connection limits | Each socket consumes memory + a file descriptor | Tune OS limits; scale out; use efficient async servers |
| Reconnection | Networks drop; mobile clients sleep | Auto-reconnect + resume with last-seen message ID |
| Scaling out | Adding a server doesn't rebalance existing connections | Graceful connection draining on deploy |
The Pub/Sub Backplane Pattern
The key architectural pattern for scaling WebSockets across many servers: servers don't talk to each other directly; they publish and subscribe through a shared message bus.
User A's socket → Server 1 Server 2 → User B's socket
│ ▲
│ publish │ subscribe
▼ │
┌───────────────────────────┐
│ Redis Pub/Sub (backplane) │
└───────────────────────────┘
A sends a message → Server 1 publishes to Redis channel →
Server 2 (subscribed) receives it → delivers to User B.
Any server can reach any user regardless of which server holds the socket.

Seeing It in Action
Scenario: A chat application's message flow.
// --- Client ---
const ws = new WebSocket("wss://chat.example.com/ws");
ws.onopen = () => ws.send(JSON.stringify({ type: "join", room: "general" }));
ws.onmessage = (e) => renderMessage(JSON.parse(e.data)); // server PUSHES
ws.onclose = () => scheduleReconnect(); // resilience
function sendChat(text) {
ws.send(JSON.stringify({ type: "msg", room: "general", text }));
}
// --- Server (pseudocode) ---
onMessage(socket, msg) {
if (msg.type === "msg") {
// Publish to the room's channel — every server subscribed to it
// will deliver to its locally-connected members of that room.
redis.publish(`room:${msg.room}`, serialize(msg));
persist(msg); // store for history/offline users
}
}
onRedisMessage(`room:general`, msg) {
for (socket of localSocketsInRoom("general"))
socket.send(msg); // fan out to this server's clients
}
Why the backplane is essential: two users in the same chat room may be connected to different servers. Without the Redis pub/sub layer, a message arriving at server 1 could never reach a user on server 2. The backplane decouples "which server holds the socket" from "who should receive the message," which is exactly what lets a stateful, connection-oriented system scale horizontally. Add persistence so users who reconnect can catch up on missed messages.
Interview Questions
Q: How does a WebSocket connection get established, and how does it differ from HTTP after that? Hint: It starts as an HTTP GET with
Upgrade: websocket/Connection: Upgradeheaders; the server replies101 Switching Protocols, and the same TCP connection is repurposed to carry WebSocket frames. After the handshake there's no HTTP request-response overhead — either side can send lightweight message frames at any time (full-duplex) until the connection closes. Usewss://(TLS) in production.Q: When would you use Server-Sent Events instead of WebSockets? Hint: When you only need server→client push (feeds, notifications, live scores). SSE is simpler, runs over plain HTTP, auto-reconnects with
Last-Event-ID, and works through most proxies. Choose WebSockets when you need genuine bidirectional, low-latency messaging (chat, gaming, collaborative editing) where the client also frequently pushes to the server.Q: Why are WebSockets harder to scale horizontally than HTTP services? Hint: They're stateful and long-lived — each connection is pinned to one server for its lifetime, breaking the "any server handles any request" model. Load balancing must keep connections sticky, adding a server doesn't rebalance existing connections, each socket consumes memory/file descriptors, and delivering a message to a user may require a server that doesn't hold their socket.
Q: How do you deliver a message to a user connected to a different server than the sender? Hint: Use a pub/sub backplane (Redis, Kafka, NATS). Servers publish messages to channels and subscribe to the ones relevant to their connected clients; the bus fans messages across all servers. This decouples "which server holds the socket" from "who should receive the message," enabling cross-server delivery and horizontal scaling.
Q: How do you make a WebSocket-based app resilient to dropped connections? Hint: Client-side auto-reconnect with exponential backoff; resume state using a last-seen message ID / sequence number so the client can request what it missed; server-side persistence of messages for offline/reconnecting users; heartbeats/pings to detect dead connections; and graceful connection draining during deploys so clients reconnect in a controlled way.
References
- MDN: WebSockets API — protocol and browser API
- RFC 6455: The WebSocket Protocol — the authoritative spec
- High Performance Browser Networking — WebSocket chapter — performance and trade-offs
Dive Deeper
- Ably: WebSockets at scale — the operational challenges in depth
- Server-Sent Events vs WebSockets — choosing the right real-time transport
- Slack: real-time messaging architecture — WebSockets and backplanes in production