WebSocket Patterns

WebSockets provide a bidirectional, full-duplex communication channel between client and server. Once established, both sides can send messages independently. The protocol upgrades from HTTP to a long-lived TCP connection.

This page is about the patterns that make WebSocket applications work in production — not the protocol details, but the operational concerns.

When WebSocket is right

When WebSocket is wrong

Connection lifecycle

Client → HTTP GET with Upgrade headers → Server
Server → HTTP 101 Switching Protocols → Client
Connection upgraded to WebSocket
... message exchange ...
Either side → close frame → Other side

The HTTP-to-WebSocket upgrade requires:

Most clients/servers handle this automatically.

Authentication

WebSocket authentication is done during the upgrade. Several patterns:

If your app already uses cookies for auth, they apply to the upgrade request. Simple.

Token in subprotocol

new WebSocket('wss://api/ws', ['token.your-jwt-here'])

The token is in the Sec-WebSocket-Protocol header. Cleaner than URL parameters.

Token in URL

wss://api/ws?token=...

Works but logged everywhere; not great for sensitive tokens.

Post-connect authentication

Connect first, then send an auth message:

{ "type": "auth", "token": "..." }

Server keeps the connection open only after successful auth. Useful for protocols where the upgrade itself is unauthenticated.

Backpressure

What happens when the server sends faster than the client consumes? Or vice versa?

The TCP layer provides some backpressure (the OS buffers fill; sends block). Above that:

For real-time but loss-tolerant streams (cursor positions, presence), drop on overflow. For streams where every message matters, slow down or disconnect.

Heartbeats

Long-idle WebSocket connections may be closed by NAT, proxies, or network equipment. Heartbeats keep the connection alive and detect failures.

Client → ping (every 30s) → Server
Server → pong → Client

If a pong is missed, reconnect. Specific timeouts depend on your infrastructure.

WebSocket has built-in ping/pong frames; some libraries handle this automatically.

Reconnection

Always assume disconnects happen. Reconnection logic:

  1. Detect disconnect (heartbeat failure, close frame, error)
  2. Wait with exponential backoff (start at 1s, max ~30s)
  3. Reconnect; re-authenticate
  4. Resync state (request "what did I miss")

The "resync state" step is application-specific. Some patterns:

Fan-out

Broadcasting to many connected clients. The challenge: each connection holds a TCP socket; servers can hold thousands but not millions.

For very high fan-out:

Library and framework support

Socket.IO adds features (auto-reconnect, fallbacks to long-polling, rooms) at the cost of being its own protocol on top of WebSocket. Use vanilla WebSocket unless Socket.IO's features are specifically needed.

Common operational issues

Sticky sessions

If clients connect to one server and you have multiple servers, the connection is server-pinned. Load balancers need sticky sessions or the connection breaks.

For stateless WebSocket usage (using a pub/sub backend for shared state), any server can handle any connection — sticky sessions optional.

Memory pressure

Each connection consumes memory. Estimate ~10-50 KB per idle connection plus per-message buffers. 100K concurrent connections = 1-5 GB just in baseline memory.

Connection limits

Each TCP connection consumes a file descriptor. Tune ulimit -n and OS limits accordingly.

Common failure patterns

Further Reading