The Problem: A Cricket Score That Keeps Asking "Any Update?"
Open a live cricket score app during an India match and watch the network activity in your phone's data usage. Older apps — and many still today — send a request to the server roughly every two seconds: "Any change in the score?" Almost every single time, the server's honest answer is "No." Only once every few minutes, when a boundary or a wicket actually happens, is the answer "Yes, here is the new score." The app has been asking the same question hundreds of times just to catch a handful of real updates. This technique is called short polling, and it is the starting point for understanding why WebSocket exists.
Short polling works, but it is wasteful in a very specific, countable way, and by the end of this chapter you will be able to calculate exactly how wasteful. More importantly, you will understand the protocol that Indian apps like live train-running-status trackers, UPI payment-confirmation screens, multiplayer quiz platforms, and chat applications actually use instead: the WebSocket.
From Letters to a Phone Call: Building the Right Mental Model
Before any formal definition, think about two ways two people can exchange information.
The first is exchanging letters by post. You write a letter, seal it, address it, and send it. The other person reads it, writes a reply, and posts it back. Every single exchange requires a fresh envelope, a fresh address, a fresh stamp — even if all you wanted to say was "no update." If you want to have a fast back-and-forth conversation this way, you would need to send a new letter every few seconds, and most of those letters would say nothing new.
The second way is a phone call. You dial once. The line opens. Now either person can speak at any moment, without re-dialing, without re-stating who they are, without paying the "connection cost" again. Silence is free — you don't need to hang up and redial just to check if the other person has something to say. When someone does have something to say, they simply say it, instantly, over the already-open line.
Ordinary web requests (the kind your browser makes when you load a page, or when JavaScript calls fetch()) behave like the postal system: every request is a fresh, self-contained transaction with its own "envelope" of headers, and the connection is typically torn down or reused fresh for the next unrelated request. Short polling is what happens when you try to fake a live conversation using letters — you keep mailing "anything new?" every two seconds.
A WebSocket is the phone call. You "dial" once — a special connection request — and after that, both the browser and the server can send messages to each other at any moment, in either direction, over the same connection, without re-introducing themselves each time. This property is called being full-duplex: both sides can talk and listen simultaneously over one continuously open channel, unlike a walkie-talkie (half-duplex, one direction at a time) or a letter (request, then wait for a reply, then request again).
What a WebSocket Actually Is
Formally: a WebSocket is a communication protocol, standardized in 2011 as RFC 6455, that establishes a single, persistent, full-duplex connection between a client and a server over TCP. It begins its life disguised as an HTTP request, then "upgrades" into its own protocol. Its URL scheme is ws:// for an unencrypted connection (default port 80) and wss:// for an encrypted connection over TLS (default port 443) — the WebSocket equivalent of the jump from http:// to https://, and just as important to use in production, since browsers refuse to open an insecure ws:// connection from an https:// page.
Two words matter most here: persistent (the connection stays open — it is not re-created for every message, unlike ordinary HTTP requests) and full-duplex (data flows both ways at once, not just server-to-client or client-to-server).
The Handshake: How a WebSocket Is Born Inside an HTTP Request
A WebSocket connection does not start as something exotic. It starts as a perfectly normal HTTP GET request, carrying a few special headers that ask the server to switch protocols mid-conversation. Here is exactly what the browser sends when your JavaScript code opens a connection to wss://server.example.com/chat:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Read this line by line. Upgrade: websocket and Connection: Upgrade together tell the server: "This looks like an HTTP request, but I actually want you to change the rules of this connection once you reply." Sec-WebSocket-Key is a random, one-time value the browser generates for this specific handshake — its job is not secrecy, but to prove to the browser that the server it's talking to genuinely understood and processed this exact WebSocket request (protecting against certain misconfigured proxies that might otherwise accept the upgrade without really supporting it). Sec-WebSocket-Version: 13 states which version of the protocol the browser speaks (13 is the final version defined by RFC 6455, and the one every modern browser uses).
If the server supports WebSockets on that route, it replies not with the usual 200 OK, but with:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Status code 101 is the key detail — it exists specifically to mean "I am switching this connection to a different protocol than HTTP, starting now." The server proves it correctly understood the request by computing Sec-WebSocket-Accept: it takes the client's key, appends a fixed constant string defined by the specification (258EAFA5-E914-47DA-95CA-C5AB0DC85B11), hashes the result with SHA-1, and encodes it in base64. You are not expected to hand-compute a SHA-1 hash — the numbers above are the exact worked example from the official specification itself, so you can trust that this precise key always produces this precise accept value. What matters is the logic: the server cannot fake this reply without actually having read the client's key, so the handshake is a genuine, verifiable agreement — after which the TCP connection stops behaving like HTTP entirely and starts carrying WebSocket frames instead.
Doing the Arithmetic: Why Polling Doesn't Scale
Now the numbers promised earlier. Suppose a cricket-score app polls the server every 2 seconds for 10 minutes of play, and — being realistic about typical HTTP header overhead for a small request/response pair like this — each poll costs about 500 bytes just in headers (method line, host, cookies, response status line, content-type, and so on), before counting a single byte of the actual score data.
- 10 minutes = 600 seconds.
- Polling every 2 seconds → 600 ÷ 2 = 300 requests.
- 300 requests × 500 bytes of header overhead = 150,000 bytes.
- 150,000 ÷ 1024 ≈ 146.5 KB spent purely on asking "anything new?" — for a match where, say, only 10 genuine score changes happened in those 10 minutes.
Now compare a WebSocket serving the same 10 real updates. It pays the handshake cost exactly once (roughly 500 bytes, similar to one HTTP exchange), and after that, every update is sent as a WebSocket frame — a much smaller unit than a full HTTP request. A frame carrying a small JSON payload under 126 bytes needs only a 2-byte header when sent from server to client (WebSocket frames from client to server must additionally carry a 4-byte masking key, a security measure required by the specification so that data client browsers send cannot be crafted to look like plain-text content to poorly configured intermediate proxies; server-to-client frames are never masked). So: 500 bytes handshake + 10 updates × (2 bytes frame header + roughly 50 bytes of JSON) ≈ 500 + 520 = 1,020 bytes, about 1 KB — against polling's 146.5 KB to deliver the identical 10 pieces of real information. That is roughly a 140-times reduction, and the gap only widens the longer the connection stays open with infrequent updates, because polling cost grows with time regardless of how much actually happens, while WebSocket cost grows only with how much actually happens.
Sending and Receiving: The WebSocket API in the Browser
Every modern browser exposes a built-in WebSocket object — no library needed for the client side. Here is a complete, working example connecting to a chat server:
const socket = new WebSocket("wss://chat.example.in/room/42");
socket.addEventListener("open", () => {
console.log("Connected");
socket.send(JSON.stringify({ type: "join", user: "Aisha" }));
});
socket.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
console.log("Received:", data.type, data.payload);
});
socket.addEventListener("close", (event) => {
console.log("Closed. Code:", event.code, "Reason:", event.reason);
});
socket.addEventListener("error", () => {
console.log("Something went wrong");
});
Trace through what actually happens. The moment new WebSocket(...) runs, the browser begins the HTTP-Upgrade handshake described above, in the background, without blocking your code. The connection object starts in the CONNECTING state (numeric value 0). If the handshake succeeds — server replies 101 with a matching Sec-WebSocket-Accept — the state becomes OPEN (1) and the "open" event fires, which is when the code above sends its first message: a JSON string describing a "join" event. Because WebSocket messages are just strings (or binary data) with no built-in structure, JSON.stringify and JSON.parse are the standard way both sides agree on shape. Every time the server sends a frame back — say, another user's chat message — the "message" event fires with event.data holding exactly what the server sent, as text. If the network drops or either side closes the connection, the state becomes CLOSING (2) and then CLOSED (3), firing "close" with a numeric code — for example 1000 means a clean, intentional closure, while 1006 means the connection was lost abnormally, without a proper close frame, which is exactly the signal you use to decide whether to attempt a reconnect.
Designing a Message Protocol: You Must Invent Your Own Structure
A crucial, often-missed point: WebSocket only guarantees you can send and receive strings (or binary blobs) reliably, in order, over one connection. It has no opinion on what those strings mean. Every real system built on WebSockets — chat apps, live scoreboards, collaborative editors — defines its own small message protocol on top, almost always JSON with a type field so the receiver knows how to handle the payload:
// Chat message from client to server
{ "type": "message", "payload": { "text": "Kya haal hai?" } }
// Score update pushed from server to every connected client
{ "type": "score_update", "payload": { "team": "IND", "runs": 187, "wickets": 4, "over": 18.3 } }
// Server telling a client someone left
{ "type": "user_left", "payload": { "user": "Rohan" } }
On the receiving side, code typically dispatches on data.type with a switch statement, calling a different handler function for each kind of message — exactly the same "look at a tag, then branch" pattern you already use for conditional logic elsewhere in programming, just applied to network messages instead of local values.
Keeping the Connection Alive: Ping/Pong, and a Common Misconception
Because a WebSocket connection can sit open for hours with no messages, network equipment in between (routers, load balancers, proxies) may silently drop it after a period of inactivity, assuming it's dead. The WebSocket protocol itself defines a lightweight fix: special control frames called Ping and Pong. Either side can send a Ping frame; the receiver is required by the specification to reply with a Pong frame automatically, proving the connection is still alive, without involving any application data.
Common misconception, worth correcting explicitly: many students assume that since the browser's WebSocket object represents the connection, you can just call something like socket.ping() from your JavaScript to keep it alive. You cannot. The browser's WebSocket API (the standard used by Chrome, Firefox, Safari, Edge) deliberately does not expose any method to send a Ping frame from client-side JavaScript — the browser only automatically responds to Pings the server sends, by sending Pongs back, invisibly, without your code even seeing it happen. Ping/Pong control at this low level is a server-side and protocol-level tool, available in server libraries like Node's popular ws package (socket.ping() exists there, on the server), but not in browser JavaScript. Because of this asymmetry, real client-side "keep-alive" is usually done one level up, at the application layer: the client periodically sends its own ordinary JSON message, like { "type": "heartbeat" }, every 30 seconds, and the server replies in kind — a manual, application-defined heartbeat riding on top of ordinary messages, distinct from (and necessary because of the limits of) the protocol's built-in Ping/Pong.
When Connections Drop: Reconnecting with Exponential Backoff
Mobile networks in particular — switching between Jio and Airtel towers, entering a metro tunnel, walking into a lift — drop connections constantly. A well-built real-time system must detect the drop (the "close" event, especially with code 1006) and reconnect automatically. But reconnecting instantly, in a tight loop, the moment a connection fails is dangerous: if the server itself is overloaded and that's why connections are failing, thousands of clients hammering it with instant reconnect attempts makes the problem worse, not better. The standard fix is exponential backoff: wait a little longer after each failed attempt, doubling the wait time, up to some maximum.
function connect(url, attempt = 0) {
const socket = new WebSocket(url);
socket.addEventListener("open", () => {
attempt = 0; // reset once we're actually connected again
});
socket.addEventListener("close", () => {
const delay = Math.min(1000 * 2 ** attempt, 30000);
setTimeout(() => connect(url, attempt + 1), delay);
});
return socket;
}
connect("wss://chat.example.in/room/42");
Trace the delay for consecutive failures, using the fact that 2 ** attempt is just 2 raised to a power — exactly the exponent rules you already use in algebra:
- attempt 0: 1000 × 2⁰ = 1000 × 1 = 1,000 ms = 1 second
- attempt 1: 1000 × 2¹ = 1000 × 2 = 2,000 ms = 2 seconds
- attempt 2: 1000 × 2² = 1000 × 4 = 4,000 ms = 4 seconds
- attempt 3: 1000 × 2³ = 1000 × 8 = 8,000 ms = 8 seconds
- attempt 4: 1000 × 2⁴ = 1000 × 16 = 16,000 ms = 16 seconds
- attempt 5: 1000 × 2⁵ = 1000 × 32 = 32,000 ms, but
Math.min(32000, 30000)caps it at 30 seconds
From here on every attempt waits exactly 30 seconds, because the cap keeps winning the comparison inside Math.min. This pattern — doubling a delay each failure, with a ceiling — is standard practice in real production systems (it is, for instance, exactly the shape of backoff strategy documented by major cloud providers for retrying failed API calls), and it is a clean, concrete example of exponential growth capped by a maximum, expressed directly in code.
Broadcasting: How One Server Talks to Many Clients
A single WebSocket connects exactly one client to the server. A real-time chat room or live-score feed needs the server to hold many such connections open simultaneously and forward a message from one client (or from itself) to all of them. On the server side (using Node.js with the widely used ws library), this looks like:
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
const clients = new Set();
server.on('connection', (socket) => {
clients.add(socket);
socket.on('message', (data) => {
for (const client of clients) {
if (client !== socket && client.readyState === WebSocket.OPEN) {
client.send(data);
}
}
});
socket.on('close', () => {
clients.delete(socket);
});
});
Trace it: every new connection is added to a Set called clients — a Set is used rather than an array specifically because removing a disconnected client (clients.delete(socket)) needs to be fast and exact, without searching. Whenever any one client sends a message, the server loops over every currently connected client and forwards the same data to each one — except back to the original sender (client !== socket), and only if that client's connection is actually still OPEN (protecting against sending into a connection that is mid-way through closing). This loop is the entire mechanism behind "everyone in the chat room sees every message instantly" — there is no magic beyond iterating over open connections and calling send() on each.
WebSocket vs. Long Polling vs. Server-Sent Events: Choosing the Right Tool
WebSocket is powerful, but it is not automatically the right choice for every real-time-feeling feature, and CBSE-style conceptual questions often test exactly this judgment:
- Short polling (repeat a request every N seconds): simplest to build, works everywhere, but wastes bandwidth and adds latency up to N seconds — acceptable only for low-urgency, infrequent updates.
- Long polling: the client sends a request, but the server deliberately holds it open, not replying until there is actually new data (or a timeout passes), then the client immediately re-requests. Better than short polling — far fewer wasted round trips — but still pays a fresh HTTP request's overhead for every single update, and is still fundamentally client-initiated.
- Server-Sent Events (SSE): a single long-lived HTTP connection where the server can push text updates to the client whenever it wants, using the browser's built-in
EventSourceAPI, which even reconnects automatically. SSE is simpler to build than a WebSocket server and is genuinely well suited to feeds that only flow one way, server-to-client — a stock ticker, a live score feed, a notification stream. Its real limitation is that it is one-directional: the client cannot send data back over that same connection, only via separate ordinary HTTP requests. - WebSocket: the only one of the four offering true full-duplex communication over a single connection — necessary the moment the client also needs to send frequent, low-latency data back, such as a chat message, a move in a multiplayer game, or live cursor positions in a collaborative document editor.
The exam-relevant judgment: if data only ever needs to flow server-to-client, SSE is usually the simpler, sufficient choice; WebSocket earns its extra complexity specifically when the client must also push data back with the same immediacy.
Check Your Understanding
- A live score app polls every 3 seconds for 5 minutes. How many requests does it send in total? (300 seconds ÷ 3 = 100 requests.)
- What HTTP status code signals a successful WebSocket handshake, and what does it literally mean? (101 Switching Protocols — the server agrees to stop speaking HTTP and start speaking WebSocket over this same TCP connection.)
- Why must frames sent from a browser to a server be masked, but frames from server to browser are not? (Masking client-to-server frames is a security requirement of the specification, protecting against attacks where crafted WebSocket payloads could be misinterpreted as valid HTTP by a misconfigured intermediary; the server is trusted not to need this protection in the other direction.)
- Using the exponential backoff formula
1000 × 2^attemptcapped at 30000 ms, what is the wait before the 4th reconnect attempt (attempt index 3)? (1000 × 2³ = 8000 ms = 8 seconds.) - True or false: you can call
socket.ping()in browser JavaScript to keep a WebSocket alive. (False — that method exists on server-side libraries like Node'sws, not on the browser's built-in WebSocket object; browsers only auto-reply to server-sent pings.) - A feature only needs to push live notifications from server to client, never the reverse. Which is the simpler correct choice: WebSocket or Server-Sent Events, and why?
Summary
A WebSocket begins as an ordinary HTTP request carrying Upgrade: websocket and a Sec-WebSocket-Key, and becomes a genuinely different, persistent, full-duplex protocol the instant the server answers with 101 Switching Protocols and a matching Sec-WebSocket-Accept. Unlike short polling, whose cost scales with elapsed time regardless of how much actually happens, a WebSocket's cost scales with real events — a difference you can now compute in bytes, not just describe in words. On top of the raw connection, real systems layer their own JSON message protocol with a type field, handle connection loss with exponential backoff so reconnect storms don't overwhelm a struggling server, and — on the server side — hold a collection of open connections to broadcast messages to many clients at once. Ping/Pong keeps idle connections alive at the protocol level, but browser JavaScript cannot trigger it directly, which is why application-level heartbeat messages exist. And WebSocket is one tool among several: when data only needs to travel from server to client, Server-Sent Events is usually the simpler correct answer; WebSocket is the right call specifically when both sides need to speak, instantly, over the same open line.