WebSockets

intermediate 2-4 YOE WebSocket real-time bidirectional HTTP scaling

HTTP was built for a simple pattern: the client asks, the server answers. But what if the server needs to push data to the client without being asked? Think chat messages, live notifications, stock price tickers, or multiplayer games. The server can’t wait for the client to ask “any new messages?” every second. We need a way for both sides to send data whenever they want. That’s what WebSockets are for.

HTTP vs WebSocket

HTTP vs WebSocket
HTTP (Request-Response)
Client → GET /messages → Server
Client ← 200 OK + data ← Server
connection closed
Client → GET /messages → Server
Client ← 200 OK + data ← Server
connection closed
WebSocket (Persistent)
Client → HTTP Upgrade → Server
═══ connection stays open ═══
Client → "hello!"
"hey there!" ← Server
"new message!" ← Server
Client → "typing..."
═══ still open ═══

With HTTP, the client always initiates. The server can only respond — it can’t proactively send data. Every exchange opens a new connection, sends data, and closes.

With WebSocket, we open a connection once and both sides can send data anytime. The connection stays open for as long as needed — minutes, hours, even days.

The WebSocket Handshake

WebSocket connections start as a regular HTTP request, then “upgrade” to a WebSocket connection:

Client → Server:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Server → Client:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

After the 101 Switching Protocols response, the connection is upgraded. From here on, both sides communicate using WebSocket frames — lightweight binary packets, not HTTP.

Common Use Cases

  • Chat applications — Slack, Discord, WhatsApp Web. Messages need to arrive instantly.
  • Live notifications — Facebook notifications, Gmail new email alerts.
  • Real-time dashboards — Stock tickers, monitoring dashboards, live sports scores.
  • Collaborative editing — Google Docs, Figma. Multiple users editing the same document.
  • Multiplayer gaming — Player positions, actions, game state updates.
  • Live streaming — Chat alongside a video stream (Twitch chat).

The common thread: all of these need the server to push data to the client, and latency matters.

A Simple WebSocket Example

Client-side (JavaScript):

const ws = new WebSocket('wss://chat.example.com/ws');

ws.onopen = () => {
  console.log('Connected!');
  ws.send(JSON.stringify({ type: 'join', room: 'general' }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log('New message:', message);
};

ws.onclose = () => {
  console.log('Disconnected. Reconnecting...');
  // reconnect logic here
};

Server-side (Node.js):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data);
    // broadcast to all connected clients
    wss.clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(msg));
      }
    });
  });
});

Scaling WebSocket Connections

Here’s where it gets tricky. Each WebSocket connection is a persistent TCP connection held in memory. A single server might handle 10,000-100,000 connections. But what happens when we have multiple servers?

The Problem

User A connects to Server 1. User B connects to Server 2. User A sends a message to User B. But Server 1 doesn’t know about User B — that connection lives on Server 2.

Solution 1: Sticky Sessions

Route each user to the same server every time using a load balancer. Simple but limits our ability to distribute load evenly.

Solution 2: Pub/Sub Backbone

All servers subscribe to a shared message bus (Redis Pub/Sub, Kafka, NATS). When Server 1 receives a message, it publishes to the bus. Server 2 picks it up and delivers to User B.

User A → Server 1 → Redis Pub/Sub → Server 2 → User B
                   → Server 3 → User C
                   → Server 1 → User D

This is the standard pattern. Redis Pub/Sub is the most common choice for chat-scale applications.

Solution 3: Managed WebSocket Services

Skip the complexity and use a managed service:

  • AWS API Gateway WebSocket — Serverless WebSocket support
  • Pusher / Ably — Real-time messaging as a service
  • Socket.IO — Library that handles reconnection, fallbacks, and rooms

Connection Management

WebSocket connections are long-lived, so we need to handle:

Heartbeats (ping/pong): The server sends periodic pings. If the client doesn’t respond with a pong, the connection is dead — close it and free resources.

Reconnection: Connections drop all the time (network changes, phone going to sleep). Clients must implement auto-reconnect with exponential backoff.

Authentication: We can’t send auth headers on every message like HTTP. Authenticate during the handshake or in the first message after connecting.

Connection limits: Each connection uses memory. Set a max per server and reject new connections gracefully when full.

When NOT to Use WebSockets

WebSockets aren’t always the answer:

  • Occasional updates (new email every few minutes) → Server-Sent Events or long polling are simpler
  • One-directional server push → SSE is lighter weight
  • Standard CRUD operations → REST is simpler and cacheable
  • Firewalls and proxies — Some corporate networks block WebSocket connections

Key Takeaway

In simple language, WebSockets give us a persistent, two-way connection between client and server. Instead of the client constantly asking “anything new?”, the server can push data whenever it wants. They’re essential for real-time features like chat, live notifications, and collaborative editing. The main challenge is scaling — each connection lives on a specific server, so we need a pub/sub layer (like Redis) to bridge messages across servers.