Networking Interview Prep
Networking for Web Developers

WebSockets & Real-Time Communication

WebSockets & Real-Time Communication

LinkedIn Hook

HTTP is great for asking questions. But what happens when the server needs to tell YOU something?

You could poll — ask the server every second: "Anything new?" That works. It's also wasteful and slow.

Or you could use WebSockets — hold the line open and let the server shout whenever something changes.

This is why Slack messages appear instantly. Why stock tickers update in real time. Why your game registers your move without refresh.

Lesson 8.3 of my Networking Interview Prep series covers: how WebSocket connections are established, what full-duplex means, when to use WebSockets vs Server-Sent Events, and the most common interview questions on real-time communication.

Read the full lesson → [link]

#WebSockets #RealTime #WebDevelopment #InterviewPrep #Networking #BackendDevelopment


WebSockets & Real-Time Communication thumbnail


What You'll Learn

  • Why HTTP's request-response model breaks down for real-time applications
  • How WebSocket connections are established via an HTTP upgrade handshake
  • What full-duplex means and why it matters
  • How to implement WebSocket client and server in JavaScript
  • When to use WebSockets, Server-Sent Events (SSE), or Long Polling
  • What the WebSocket frame format looks like under the hood

The Telephone vs Letter Analogy

HTTP works like sending letters. You write a letter (request), mail it, and wait for a reply (response). The conversation is always client-initiated. If the post office (server) discovers something important, it cannot contact you — it has to wait for your next letter.

WebSockets work like a telephone call. Once you dial (handshake), both sides have a live, two-way channel. Either side can speak at any moment. The server can push a message without waiting for a request.

For letter exchanges, HTTP is perfect. For conversations, you need a telephone.


The Problem with HTTP for Real-Time

HTTP is a half-duplex, request-driven protocol. Every exchange is initiated by the client. The server can only respond — it cannot spontaneously push data.

This breaks down for real-time scenarios:

Chat application with HTTP polling:

Client:  "Any new messages?"   → Server: "No"
Client:  "Any new messages?"   → Server: "No"
Client:  "Any new messages?"   → Server: "No"
Client:  "Any new messages?"   → Server: "Yes! Here's a message"

To simulate real-time behavior with HTTP, clients must poll — repeatedly ask the server if anything changed. This is wasteful:

  • New TCP connection (or connection reuse) per poll
  • Headers sent on every request (hundreds of bytes of overhead)
  • High latency if polling interval is too long
  • Unnecessary server load if polling interval is too short

For a chat app with 10,000 users polling every second, that is 10,000 HTTP requests per second for mostly empty responses.


What WebSockets Solve

WebSocket provides a persistent, full-duplex communication channel over a single TCP connection.

  • Persistent: connection stays open as long as both sides want it
  • Full-duplex: client and server can send messages simultaneously, without waiting for the other
  • Low overhead: after the initial handshake, WebSocket frames have only 2–14 bytes of header (vs. hundreds for HTTP)
  • Server push: the server can initiate a message at any time
Chat application with WebSockets:

[One-time handshake]
Client → Server: "Upgrade to WebSocket"
Server → Client: "101 Switching Protocols"
[Persistent connection open]

Client → Server: "Hello!"              (instant, no headers)
Server → Client: "Hi there!"          (server-initiated, instant)
Server → Client: "User X just joined" (server-initiated, no request needed)
Client → Server: "Goodbye"
[Either side closes]

The WebSocket Handshake

WebSocket connections start as HTTP — they use a special HTTP upgrade mechanism:

Client sends (HTTP GET with upgrade headers):
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com

Server responds (if it accepts):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

101 Switching Protocols is the only status code that says "the protocol being used on this connection is changing." After this response, HTTP is gone — the connection is now a WebSocket channel.

Sec-WebSocket-Key / Sec-WebSocket-Accept: the client sends a random base64 key; the server concatenates it with a magic GUID, SHA-1 hashes the result, and base64 encodes it. This confirms the server intentionally performed the upgrade (prevents malicious proxies from creating WebSocket connections accidentally).

After the handshake:

  • Communication is on the same TCP connection that carried the HTTP upgrade
  • The connection runs on port 80 (ws://) or 443 (wss://)
  • wss (WebSocket Secure) runs WebSocket over TLS, just like HTTPS

The WebSocket Frame Format

Once established, data travels in frames — not HTTP headers/bodies. A minimal frame:

WebSocket Frame Header (2-14 bytes):
 Bit: 0        7 8      15 16                   31
      +-+-+-+-+-------+-+-------------+...--------+
      |F|R|R|R| opcode|M| Payload len | Extended  |
      |I|S|S|S|       |A|    (7 bits) | length... |
      |N|V|V|V|       |S|             |           |
      | |1|2|3|       |K|             |           |
      +-+-+-+-+-------+-+-------------+...--------+

Opcode meanings:
  0x0 = Continuation frame
  0x1 = Text frame (UTF-8)
  0x2 = Binary frame
  0x8 = Connection Close
  0x9 = Ping
  0xA = Pong

For a "Hello" message, the WebSocket frame overhead is 6 bytes (for client-to-server, masking adds 4 bytes). Compare to HTTP where "Hello" would carry hundreds of bytes of headers.


Building a WebSocket Server and Client

// Server — Node.js with 'ws' library
const WebSocket = require("ws");

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

wss.on("connection", (socket, request) => {
  console.log("Client connected:", request.socket.remoteAddress);

  // Send a message to this client
  socket.send(JSON.stringify({ type: "welcome", message: "Connected!" }));

  // Receive messages from this client
  socket.on("message", (data) => {
    const parsed = JSON.parse(data.toString());
    console.log("Received:", parsed);

    // Broadcast to ALL connected clients
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
          type: "chat",
          from: parsed.username,
          message: parsed.message,
          timestamp: Date.now()
        }));
      }
    });
  });

  socket.on("close", (code, reason) => {
    console.log("Client disconnected:", code, reason.toString());
  });

  socket.on("error", (err) => {
    console.error("Socket error:", err.message);
  });
});
// Client — Browser
const socket = new WebSocket("wss://example.com/chat");

// Connection opened
socket.addEventListener("open", () => {
  console.log("Connected to server");
  socket.send(JSON.stringify({
    type: "join",
    username: "Rakibul"
  }));
});

// Receive messages from server (server-initiated!)
socket.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  if (data.type === "chat") {
    displayMessage(data.from, data.message);
  }
});

// Send a message when user presses Enter
function sendMessage(text) {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({
      type: "chat",
      username: "Rakibul",
      message: text
    }));
  }
}

// Connection closed
socket.addEventListener("close", (event) => {
  console.log("Disconnected:", event.code, event.reason);
  // Reconnect logic here if needed
});

WebSocket Connection States

The browser WebSocket API exposes four readyState values:

WebSocket.CONNECTING  // 0 — handshake in progress
WebSocket.OPEN        // 1 — connection established, can send/receive
WebSocket.CLOSING     // 2 — close handshake initiated
WebSocket.CLOSED      // 3 — connection fully closed

Always check readyState === WebSocket.OPEN before calling .send(). Calling send on a closed socket throws an error.


Alternatives to WebSockets

WebSockets are not always the right tool. Two simpler alternatives exist for specific patterns:

Server-Sent Events (SSE)

SSE is a unidirectional channel — server to client only. The client opens one long-lived HTTP connection and the server streams events down it.

// Server — SSE with Node.js / Express
app.get("/events", (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  // Send an event every 2 seconds
  const interval = setInterval(() => {
    const data = JSON.stringify({ price: 142.50, time: Date.now() });
    res.write(`event: price-update\n`);
    res.write(`data: ${data}\n\n`);   // double newline ends the event
  }, 2000);

  req.on("close", () => clearInterval(interval));
});
// Client — SSE is built into browsers
const source = new EventSource("/events");

source.addEventListener("price-update", (event) => {
  const data = JSON.parse(event.data);
  updateStockTicker(data.price);
});

source.onerror = () => {
  console.log("SSE connection lost — browser auto-reconnects");
};

SSE auto-reconnects on disconnect (browser handles it). No special library needed. Works perfectly for one-way server→client streams.

Long Polling

The client sends a request and the server holds it open until something happens (or a timeout):

// Long polling — client keeps asking, server delays response until data exists
async function longPoll() {
  while (true) {
    try {
      const res = await fetch("/api/notifications/poll");
      // Server held this request for up to 30s waiting for a notification
      const notifications = await res.json();
      processNotifications(notifications);
    } catch (err) {
      await new Promise(resolve => setTimeout(resolve, 1000)); // retry after 1s
    }
  }
}

Long polling works everywhere HTTP works (no WebSocket support needed). But it has higher overhead than WebSockets and SSE for frequent updates.


Choosing the Right Real-Time Technology

ScenarioBest ChoiceWhy
Chat app, multiplayer game, trading platformWebSocketFull-duplex, low overhead, high message frequency
Live feed, notifications, dashboardsSSEServer→client only, simpler, auto-reconnect
Simple notifications, legacy browser supportLong PollingWorks everywhere, no protocol switch
Infrequent updates (every few minutes)Short PollingSimplest; low frequency makes overhead acceptable

Common Mistakes

  • Forgetting to handle disconnections and reconnect. WebSocket connections drop — network blips, server restarts, mobile apps going to background. A production WebSocket client needs reconnection logic with exponential backoff. The browser does not auto-reconnect WebSockets (SSE does, but WebSocket does not).

  • Scaling WebSockets without a message broker. A standard WebSocket server only broadcasts to clients connected to that specific server instance. If you have 3 server instances and a message arrives at instance #1, clients connected to instances #2 and #3 never see it. Fix: use a pub/sub broker (Redis Pub/Sub, Kafka) as a shared message bus. Every instance subscribes and rebroadcasts to its local clients.

  • Choosing WebSockets when SSE is sufficient. WebSockets require both client and server to implement the full bidirectional protocol. If your use case is server-only broadcasts (stock prices, news feeds, live logs), SSE is simpler, uses standard HTTP, works through most proxies, and auto-reconnects. Reach for WebSockets only when you need client-to-server messages over the same persistent channel.


Interview Questions

Q: How is a WebSocket connection established?

A WebSocket connection starts as a regular HTTP GET request with special headers: Upgrade: websocket and Connection: Upgrade. The server responds with 101 Switching Protocols. After that exchange, the TCP connection that carried HTTP is repurposed as a WebSocket channel — HTTP headers are no longer used, and data flows as lightweight binary frames. WebSocket runs on port 80 (ws://) or 443 (wss:// over TLS), same as HTTP/HTTPS.

Q: What is the difference between WebSockets and Server-Sent Events?

WebSocket is full-duplex: either side (client or server) can send messages at any time. SSE is unidirectional: the server streams events to the client, but the client cannot push data back over the same channel. SSE is built on plain HTTP (persistent chunked response), requires no protocol switch, auto-reconnects on disconnect, and is simpler to implement. Use WebSockets when clients need to send messages frequently (chat, gaming). Use SSE when only the server sends (live feeds, notifications).

Q: How do you scale WebSockets across multiple server instances?

When multiple server instances handle WebSocket connections, messages received by instance A cannot directly reach clients connected to instances B or C. The solution is a message broker (Redis Pub/Sub, Kafka, RabbitMQ). When instance A needs to broadcast, it publishes to the broker. All instances subscribe to the broker and rebroadcast to their locally connected clients. This way, every message reaches every client regardless of which server they are connected to. A sticky load balancer (session affinity) also helps — routing a client's connections to the same instance — but is not sufficient on its own for broadcast scenarios.

Q: What does readyState === WebSocket.OPEN mean and why check it?

The WebSocket readyState property reflects the current connection state: CONNECTING (0), OPEN (1), CLOSING (2), CLOSED (3). Calling .send() on a socket that is not in the OPEN state throws an InvalidStateError. Network failures, server restarts, or application code can close the connection at any point. Always check readyState === WebSocket.OPEN before calling .send(), and implement reconnection logic to handle transitions to CLOSED.


Quick Reference — Cheat Sheet

WebSocket Handshake

Client → Server:
  GET /chat HTTP/1.1
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Key: <random base64>
  Sec-WebSocket-Version: 13

Server → Client:
  HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: <derived key>

[TCP connection now speaks WebSocket protocol]

WebSocket vs SSE vs Long Polling

FeatureWebSocketSSELong Polling
DirectionFull-duplexServer→ClientClient-initiated
Protocolws:// / wss://HTTPHTTP
Header overhead2-14 bytes/frameLow (chunked)High per request
Auto-reconnectNoYesManual
Browser supportAll modernAll modernAll
Proxy friendlySometimes issuesAlwaysAlways
Best forChat, gaming, tradingFeeds, notificationsSimple notifications

WebSocket Close Codes

CodeMeaning
1000Normal closure
1001Endpoint going away (server restart)
1006Abnormal closure (connection dropped)
1008Policy violation
1011Internal server error

Client-Side WebSocket Skeleton

const socket = new WebSocket("wss://host/path");

socket.onopen    = () => socket.send(JSON.stringify({ type: "hello" }));
socket.onmessage = (e) => console.log(JSON.parse(e.data));
socket.onclose   = (e) => reconnect(e.code);
socket.onerror   = (e) => console.error(e);

// Check state before sending
if (socket.readyState === WebSocket.OPEN) {
  socket.send(data);
}

Previous: Lesson 8.2 → Next: Lesson 8.4 →


This is Lesson 8.3 of the Networking Interview Prep Course — 8 chapters, 32 lessons.

On this page