WebSockets for Real-Time Features
Reviewed & published by Brayan K
A WebSocket is a persistent, two-way connection between a browser and a server over a single TCP link, letting both sides send messages to each other instantly — ideal for chat, live updates, and other real-time features.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
WebSocket examples require a server to connect to. For the best experience: Download Node.js to run a local WebSocket server, use your browser's Developer Console (Press F12) to test the WebSocket API, or try online WebSocket testing services.
Master real-time communication: persistent connections, messaging patterns, presence, rooms, and scalable architecture.
What You'll Learn
- WebSocket connection lifecycle
- Auto-reconnection with backoff
- Heartbeat & presence systems
- Room/channel management
- Reliable delivery with ACKs
- Scaling with Pub/Sub
What WebSockets Actually Are
HTTP is request → response only. WebSockets allow persistent, two-way communication: the server can push events anytime, and the client can send events anytime — no need for repeated HTTP requests.
HTTP (Traditional)
- Request → Response → Connection closed
- Client must poll for updates
- High latency, wasted bandwidth
- Server can't push data
WebSockets
- Persistent connection stays open
- Server pushes updates instantly
- Low latency, efficient
- Perfect for real-time apps
WebSockets Power:
Chat systems • Live notifications • Real-time games • Stock tickers • Collaborative editing • Multiplayer interactions • Typing indicators • Presence systems • Live dashboards
Basic WebSocket Connection
The browser's WebSocket API is simple but powerful. Always use wss:// for secure connections.
// Basic WebSocket Connection
const socket = new WebSocket("wss://echo.websocket.org");
// Connection opened
socket.addEventListener("open", (event) => {
console.log("✅ Connected to WebSocket server!");
// Send a message
socket.send("Hello Server!");
});
// Listen for messages
socket.addEventListener("message", (event) => {
console.log("📨 Message from server:", event.data);
});
// Connection closed
socket.addEventListener("close", (event) => {
console.log("❌ Disconnected from server");
console.log("Code:", event.code, "Reason:", event.reason);
});
// Handle errors
socket.addEventListener("error", (error) => {
console.error("⚠️ WebSocket error:", error);
});
// Check connection state
console.log("Ready state:", socket.readyState);
// 0 = CONNECTING
// 1 = OPEN
// 2 = CLOSING
// 3 = CLOSED
// Simulated for demo
console.log("\n💡 WebSocket states:");
console.log("CONNECTING:", WebSocket.CONNECTING);
console.log("OPEN:", WebSocket.OPEN);
console.log("CLOSING:", WebSocket.CLOSING);
console.log("CLOSED:", WebSocket.CLOSED);Real-Time Messaging with JSON
WebSockets only send text or binary. Best practice is to use structured JSON messages with type fields to handle different event types.
// Real-Time Messaging with JSON
// Message types for a chat app
const MessageTypes = {
CHAT: 'chat',
TYPING: 'typing',
PRESENCE: 'presence',
JOIN_ROOM: 'join_room',
LEAVE_ROOM: 'leave_room',
ACK: 'ack',
ERROR: 'error'
};
// Create a structured message
function createMessage(type, payload) {
return JSON.stringify({
id: crypto.randomUUID(),
type,
payload,
timestamp: Date.now()
});
}
// Send chat message
const chatMessage = createMessage(MessageTypes.CHAT, {
text: "Hello everyone!",
room: "general",
user: "Boopie"
});
console.log("Chat message:", chatMessage);
// Send typing indicator
const typingMessage = createMessage(MessageTypes.TYPING, {
room: "general",
user: "Boopie",
isTyping: true
});
console.log("\nTyping message:", typingMessage);
// Parse incoming messages safely
function parseMessage(data) {
try {
const msg = JSON.parse(data);
// Validate required fields
if (!msg.type || !msg.payload) {
throw new Error("Invalid message structure");
}
return msg;
} catch (error) {
console.error("Failed to parse message:", error.message);
return null;
}
}
// Handle different message types
function handleMessage(data) {
const msg = parseMessage(data);
if (!msg) return;
switch (msg.type) {
case MessageTypes.CHAT:
console.log(`[${msg.payload.user}]: ${msg.payload.text}`);
break;
case MessageTypes.TYPING:
console.log(`${msg.payload.user} is typing...`);
break;
case MessageTypes.PRESENCE:
console.log(`${msg.payload.user} is ${msg.payload.status}`);
break;
default:
console.log("Unknown message type:", msg.type);
}
}
// Test the handler
handleMessage(chatMessage);
handleMessage(typingMessage);Robust Reconnection Logic
WebSocket connections will always drop eventually (network issues, server restarts, etc.). Production apps must auto-recover with exponential backoff.
// Robust Reconnection Logic with Exponential Backoff
class ReconnectingWebSocket {
constructor(url, options = {}) {
this.url = url;
this.maxRetries = options.maxRetries || 10;
this.baseDelay = options.baseDelay || 1000;
this.maxDelay = options.maxDelay || 30000;
this.retries = 0;
this.socket = null;
this.listeners = new Map();
this.messageQueue = [];
this.connect();
}
connect() {
console.log(`Connecting to ${this.url}...`);
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log("✅ Connected!");
this.retries = 0;
// Send queued messages
while (this.messageQueue.length > 0) {
const msg = this.messageQueue.shift();
this.socket.send(msg);
}
this.emit("open");
};
this.socket.onclose = (event) => {
console.log("❌ Connection closed:", event.code);
this.emit("close", event);
if (this.retries < this.maxRetries) {
this.scheduleReconnect();
} else {
console.error("Max retries reached. Giving up.");
this.emit("maxRetries");
}
};
this.socket.onerror = (error) => {
console.error("WebSocket error:", error);
this.emit("error", error);
};
this.socket.onmessage = (event) => {
this.emit("message", event);
};
}
scheduleReconnect() {
this.retries++;
// Exponential backoff with jitter
const delay = Math.min(
this.baseDelay * Math.pow(2, this.retries - 1) + Math.random() * 1000,
this.maxDelay
);
console.log(`Reconnecting in ${Math.round(delay)}ms (attempt ${this.retries})`);
setTimeout(() => this.connect(), delay);
}
send(data) {
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(data);
} else {
// Queue message for when connection is restored
this.messageQueue.push(data);
console.log("Message queued (not connected)");
}
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(cb => cb(data));
}
close() {
this.maxRetries = 0; // Prevent reconnection
this.socket?.close();
}
}
// Usage example
console.log("Creating reconnecting WebSocket...");
const ws = new ReconnectingWebSocket("wss://example.com/chat");
ws.on("open", () => console.log("Ready to send messages!"));
ws.on("message", (event) => console.log("Received:", event.data));
ws.on("maxRetries", () => console.log("Connection failed permanently"));
// Simulate sending
ws.send(JSON.stringify({ type: "ping" }));
console.log("\n💡 Messages are queued if not connected!");Worked Example — Run a WebSocket Client With No Server
Everything above is real WebSocket code, and none of it can run on this page, because a WebSocket needs a live server at the other end. That is a genuine problem when you are learning: you cannot see the queueing, the routing or the reconnect logic actually happen.
So use the technique that professional teams use for exactly this reason. Write your client against a fake socket — a plain object with the same shape as a real one: the same readyState numbers, the same send(), the same onopen/onmessage/onclose. Your client cannot tell the difference, and you get to make the "server" accept, send and drop the connection whenever you like. This is how you test the awkward cases — a dropped connection mid-send — that are nearly impossible to reproduce against a real server on demand.
The program below runs a full connection lifecycle: a message written before the handshake finished, the flush when it opens, three incoming messages including one nobody handles, then the network dying. Read the output beside the code.
// WORKED EXAMPLE - a WebSocket client you can actually run.
// A real socket needs a server, which this editor has not got. So here is the
// trick professionals use to build and test socket code offline: swap the
// socket for a FAKE object with the same shape. The client cannot tell the
// difference, and you get to fire any event you like, on demand.
// ---------- The stand-in for a real WebSocket ----------
class FakeSocket {
constructor(url) {
this.url = url;
this.readyState = 0; // 0 CONNECTING, 1 OPEN, 2 CLOSING, 3 CLOSED - the real numbers
this.sent = []; // everything the client handed to send()
this.onopen = null;
this.onmessage = null;
this.onclose = null;
}
send(data) {
// A real WebSocket throws in exactly this case if you send too early.
if (this.readyState !== 1) throw new Error("InvalidStateError: socket is not open");
this.sent.push(data);
}
// ---- test-only handles. On a real socket the NETWORK causes these. ----
serverAccepts() { this.readyState = 1; if (this.onopen) this.onopen(); }
serverSends(obj) { if (this.onmessage) this.onmessage({ data: JSON.stringify(obj) }); }
serverDrops(code) { this.readyState = 3; if (this.onclose) this.onclose({ code: code, reason: "" }); }
}
// Exponential backoff with a ceiling: 1s, 2s, 4s, 8s... never past 30s.
// It is pure arithmetic, which is why it is the easiest part to get right.
function backoff(attempt) {
return Math.min(1000 * Math.pow(2, attempt - 1), 30000);
}
// ---------- The client: the part you actually write ----------
class ChatClient {
constructor(url) {
this.outbox = []; // messages written while the socket was not open
this.handlers = {}; // message type -> what to do about it
this.attempt = 0; // how many reconnects in a row
this.socket = new FakeSocket(url);
this.wire();
}
wire() {
this.socket.onopen = () => {
console.log("open: flushing " + this.outbox.length + " queued message(s)");
this.attempt = 0; // a good connection resets the backoff
while (this.outbox.length > 0) this.socket.send(this.outbox.shift());
};
this.socket.onmessage = (event) => {
// event.data is always TEXT. Never trust it to be valid JSON.
let msg;
try {
msg = JSON.parse(event.data);
} catch {
console.log("ignored a message that was not JSON");
return;
}
const handler = this.handlers[msg.type]; // route on the type field
if (!handler) {
console.log("no handler for type '" + msg.type + "'");
return;
}
handler(msg.payload);
};
this.socket.onclose = (event) => {
this.attempt++;
console.log("closed with code " + event.code + " - would retry in " + backoff(this.attempt) + "ms");
};
}
on(type, fn) { this.handlers[type] = fn; }
send(type, payload) {
const text = JSON.stringify({ type: type, payload: payload });
if (this.socket.readyState !== 1) {
this.outbox.push(text); // do not throw at the user
console.log("queued (socket not open): " + type);
return;
}
this.socket.send(text);
console.log("sent: " + type);
}
}
// ---------- Drive it through a whole connection lifecycle ----------
const client = new ChatClient("wss://example.invalid/chat");
client.on("chat", p => console.log("chat from " + p.user + ": " + p.text));
client.on("presence", p => console.log(p.user + " is now " + p.status));
client.send("chat", { user: "you", text: "anyone there?" }); // still connecting
client.socket.serverAccepts(); // handshake completes
client.send("chat", { user: "you", text: "hello!" });
client.socket.serverSends({ type: "chat", payload: { user: "ada", text: "hi" } });
client.socket.serverSends({ type: "presence", payload: { user: "ada", status: "online" } });
client.socket.serverSends({ type: "typing", payload: {} }); // nothing registered for this
client.socket.serverDrops(1006); // 1006 = lost, no close frame
client.send("chat", { user: "you", text: "still here?" }); // queued again, not lost
console.log("backoff schedule (ms): " + [1, 2, 3, 4, 5, 6].map(backoff).join(", "));
console.log("server actually received " + client.socket.sent.length + " message(s)");
// ✅ Expected output:
// queued (socket not open): chat
// open: flushing 1 queued message(s)
// sent: chat
// chat from ada: hi
// ada is now online
// no handler for type 'typing'
// closed with code 1006 - would retry in 1000ms
// queued (socket not open): chat
// backoff schedule (ms): 1000, 2000, 4000, 8000, 16000, 30000
// server actually received 2 message(s)
//
// To use this against a real server, change ONE line:
// this.socket = new FakeSocket(url); -> this.socket = new WebSocket(url);
// Nothing else moves. That is the payoff for writing the client against the
// interface rather than against the network.🎯 Your Turn — Finish the Notification Client
Same fake socket, smaller client, three blanks. Each one is a decision that real WebSocket code gets wrong regularly: which field to route on, which readyState means "safe to send", and which end of the queue to flush from.
// 🎯 YOUR TURN - fill in the three blanks marked ___
// The fake socket is given. You finish the notification client.
class FakeSocket {
constructor(url) {
this.url = url;
this.readyState = 0; // 0 CONNECTING, 1 OPEN, 2 CLOSING, 3 CLOSED
this.sent = [];
this.onopen = null;
this.onmessage = null;
}
send(data) {
if (this.readyState !== 1) throw new Error("InvalidStateError: socket is not open");
this.sent.push(data);
}
serverAccepts() { this.readyState = 1; if (this.onopen) this.onopen(); }
serverSends(obj) { if (this.onmessage) this.onmessage({ data: JSON.stringify(obj) }); }
}
const socket = new FakeSocket("wss://example.invalid/notifications");
const outbox = [];
const handlers = {};
// 1) Route each incoming message to the right handler.
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
const handler = handlers[msg.___]; // 👉 the field that says what kind of message it is
if (!handler) {
console.log("no handler for '" + msg.type + "'");
return;
}
handler(msg.payload);
};
// 2) Only an OPEN socket can send. Which readyState number means OPEN?
function send(type, payload) {
const text = JSON.stringify({ type: type, payload: payload });
if (socket.readyState !== ___) { // 👉 0 CONNECTING, 1 OPEN, 2 CLOSING, 3 CLOSED
outbox.push(text);
console.log("queued: " + type);
return;
}
socket.send(text);
console.log("sent: " + type);
}
// 3) The moment the connection opens, empty the outbox oldest-first.
socket.onopen = () => {
console.log("open with " + outbox.length + " queued");
while (outbox.length > 0) socket.send(___); // 👉 outbox.shift() takes the OLDEST item
};
handlers.alert = p => console.log("ALERT: " + p.text);
handlers.receipt = p => console.log("receipt #" + p.id);
send("subscribe", { topic: "orders" }); // before the handshake finishes
socket.serverAccepts();
send("ping", {});
socket.serverSends({ type: "alert", payload: { text: "server restarting" } });
socket.serverSends({ type: "receipt", payload: { id: 7781 } });
socket.serverSends({ type: "unknown", payload: {} });
console.log("server received " + socket.sent.length + " message(s)");
// ✅ Expected output once the blanks are filled:
// queued: subscribe
// open with 1 queued
// sent: ping
// ALERT: server restarting
// receipt #7781
// no handler for 'unknown'
// server received 2 message(s)
//
// If you get "InvalidStateError: socket is not open", blank 2 is wrong: the
// code tried to send down a socket that had not finished connecting.
// If you use outbox.pop() in blank 3 the messages come out newest-first,
// which is how chat apps end up showing replies before questions.Heartbeat (Ping/Pong) System
Every WebSocket system needs a heartbeat to detect dead connections. Clients ping periodically; if no pong returns, the connection is considered dead.
// Heartbeat (Ping/Pong) System for Dead Connection Detection
class HeartbeatWebSocket {
constructor(url, options = {}) {
this.url = url;
this.pingInterval = options.pingInterval || 30000;
this.pongTimeout = options.pongTimeout || 5000;
this.socket = null;
this.pingTimer = null;
this.pongTimer = null;
this.isAlive = false;
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log("✅ Connected - starting heartbeat");
this.isAlive = true;
this.startHeartbeat();
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "pong") {
console.log("💓 Pong received - connection alive");
this.isAlive = true;
this.clearPongTimeout();
} else {
// Handle other messages
console.log("Message:", data);
}
};
this.socket.onclose = () => {
console.log("❌ Disconnected - stopping heartbeat");
this.stopHeartbeat();
this.scheduleReconnect();
};
}
startHeartbeat() {
this.pingTimer = setInterval(() => {
if (this.socket?.readyState === WebSocket.OPEN) {
console.log("💓 Sending ping...");
this.socket.send(JSON.stringify({ type: "ping" }));
// Set timeout for pong response
this.pongTimer = setTimeout(() => {
console.log("⚠️ Pong timeout - connection might be dead");
this.isAlive = false;
this.socket.close();
}, this.pongTimeout);
}
}, this.pingInterval);
}
stopHeartbeat() {
if (this.pingTimer) {
clearInterval(this.pingTimer);
this.pingTimer = null;
}
this.clearPongTimeout();
}
clearPongTimeout() {
if (this.pongTimer) {
clearTimeout(this.pongTimer);
this.pongTimer = null;
}
}
scheduleReconnect() {
console.log("Reconnecting in 3 seconds...");
setTimeout(() => this.connect(), 3000);
}
send(message) {
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
}
}
}
// Demo
console.log("Heartbeat WebSocket Demo:");
console.log("- Ping sent every 30 seconds");
console.log("- Connection closed if no pong within 5 seconds");
console.log("- Auto-reconnect on connection loss");
// Simulate heartbeat flow
console.log("\n📡 Simulated heartbeat flow:");
console.log("1. Client sends: { type: 'ping' }");
console.log("2. Server responds: { type: 'pong' }");
console.log("3. If no pong → connection considered dead");Presence System (Online/Offline Tracking)
Presence tracking shows who's online. It requires careful handling because disconnects aren't always clean (network loss, device sleep, etc.).
// Presence System (Online/Offline Tracking)
class PresenceManager {
constructor() {
this.users = new Map();
this.listeners = [];
}
// Track user coming online
userJoined(userId, metadata = {}) {
const user = {
id: userId,
status: "online",
joinedAt: Date.now(),
lastSeen: Date.now(),
...metadata
};
this.users.set(userId, user);
this.notify("join", user);
console.log(`✅ ${userId} joined (total: ${this.users.size})`);
return user;
}
// Track user going offline
userLeft(userId) {
const user = this.users.get(userId);
if (user) {
user.status = "offline";
user.leftAt = Date.now();
this.users.delete(userId);
this.notify("leave", user);
console.log(`❌ ${userId} left (total: ${this.users.size})`);
}
}
// Update last seen (from heartbeat)
updateLastSeen(userId) {
const user = this.users.get(userId);
if (user) {
user.lastSeen = Date.now();
}
}
// Check for stale connections (no heartbeat)
cleanupStaleUsers(maxAge = 60000) {
const now = Date.now();
const stale = [];
this.users.forEach((user, id) => {
if (now - user.lastSeen > maxAge) {
stale.push(id);
}
});
stale.forEach(id => {
console.log(`⚠️ Removing stale user: ${id}`);
this.userLeft(id);
});
return stale;
}
// Get all online users
getOnlineUsers() {
return Array.from(this.users.values());
}
// Subscribe to presence changes
onPresenceChange(callback) {
this.listeners.push(callback);
}
notify(event, user) {
this.listeners.forEach(cb => cb(event, user));
}
}
// Usage
const presence = new PresenceManager();
// Subscribe to changes
presence.onPresenceChange((event, user) => {
console.log(`Presence event: ${event}`, user.id);
});
// Simulate users joining
presence.userJoined("user_1", { name: "Alice" });
presence.userJoined("user_2", { name: "Bob" });
presence.userJoined("user_3", { name: "Charlie" });
console.log("\nOnline users:", presence.getOnlineUsers().map(u => u.id));
// Simulate user leaving
presence.userLeft("user_2");
console.log("\nOnline users:", presence.getOnlineUsers().map(u => u.id));Rooms & Channels
Real apps need grouping: chat rooms, game lobbies, per-user channels. Rooms allow targeted messaging instead of broadcasting to everyone.
Reliable Delivery with ACKs
WebSockets do NOT guarantee message delivery. For critical messages, implement ACKs (acknowledgments) and retry logic.
🎯 Mini-Challenge — At-Least-Once Delivery
Now build the acknowledgement loop yourself, against the same fake socket. The rule is simple and it is what "at-least-once delivery" means in practice: a message is not finished when you send it, it is finished when the server says it arrived. Anything still unconfirmed gets sent again.
Only a brief and the test script below — no starter logic. The server acknowledges just one of the three messages, so your resend must pick out exactly the other two, in order.
// 🎯 MINI-CHALLENGE: at-least-once delivery with ACKs
//
// The internet loses messages. A production client therefore keeps every
// message it has sent until the server confirms it, and resends the rest.
//
// The fake socket below is given, and already open. You write four things:
//
// 1. An object pending mapping id -> text, and a counter that starts at 1.
// 2. sendReliable(text): take the next id, save pending[id] = text, then
// socket.send(JSON.stringify({ id: id, text: text })).
// 3. socket.onmessage: parse the JSON. If msg.type === "ack",
// delete pending[msg.id] - that message is safely delivered.
// 4. resendUnacked(): for every id still left in pending, print
// "resending #" + id + ": " + pending[id]
class FakeSocket {
constructor(url) { this.url = url; this.readyState = 0; this.sent = []; this.onmessage = null; }
send(data) {
if (this.readyState !== 1) throw new Error("InvalidStateError: socket is not open");
this.sent.push(data);
}
serverAccepts() { this.readyState = 1; }
serverSends(obj) { if (this.onmessage) this.onmessage({ data: JSON.stringify(obj) }); }
}
const socket = new FakeSocket("wss://example.invalid/chat");
socket.serverAccepts();
// your code here
sendReliable("hello");
sendReliable("how are you");
sendReliable("bye");
socket.serverSends({ type: "ack", id: 2 }); // only the middle one is confirmed
console.log("still waiting on " + Object.keys(pending).length + " message(s)");
resendUnacked();
// ✅ Expected output:
// still waiting on 2 message(s)
// resending #1: hello
// resending #3: byeWebSocket Security
WebSockets must be validated like any untrusted API. Always authenticate, validate messages, sanitize input, and rate limit.
Scaling with Pub/Sub Pattern
A single server can't handle millions of users. Real systems use Redis/NATS/Kafka as a message bus to broadcast across multiple servers.
📡 WebSocket Mastery Summary
- WebSockets provide persistent, bidirectional communication
- Use wss:// for secure connections
- Structure messages with JSON and type fields
- Implement auto-reconnection with exponential backoff
- Use heartbeats to detect dead connections
- Build presence systems for online/offline tracking
- Organize users into rooms/channels
- Use ACKs and retries for reliable delivery
- Validate, sanitize, and rate-limit all messages
- Scale with Pub/Sub (Redis) across multiple servers
Practice quiz
How does a WebSocket differ from traditional HTTP?
- It only works on localhost
- It is request-response only and closes after each reply
- It provides a persistent, two-way connection where the server can push data anytime
- It cannot send text
Answer: It provides a persistent, two-way connection where the server can push data anytime. Unlike HTTP's request-then-response model, a WebSocket keeps a persistent, bidirectional connection open so both sides can send messages anytime.
Which URL scheme is used for a secure WebSocket connection?
- wss://
- https://
- ftp://
- ws-secure://
Answer: wss://. wss:// is the secure WebSocket scheme (the WebSocket equivalent of https://).
Which event fires when a WebSocket connection is successfully established?
- 'message'
- 'close'
- 'error'
- 'open'
Answer: 'open'. The 'open' event fires when the connection is established; 'message' fires for incoming data.
Where do you read the data of an incoming WebSocket message?
- event.body
- event.data
- event.payload
- event.result
Answer: event.data. In the 'message' event handler, the received data is on event.data.
In the lesson, what does socket.readyState value 1 (OPEN) mean?
- The connection is open and ready to send
- The connection is closing
- The connection is connecting
- The connection is closed
Answer: The connection is open and ready to send. readyState 1 is OPEN (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED).
Why does the reconnection example use exponential backoff?
- To send messages faster
- To encrypt the data
- To space out reconnect attempts with increasing delays instead of hammering the server
- To avoid using JSON
Answer: To space out reconnect attempts with increasing delays instead of hammering the server. Exponential backoff increases the delay between reconnect attempts, avoiding overwhelming a struggling server.
What is the purpose of a heartbeat (ping/pong) system?
- To compress messages
- To detect dead connections when no pong returns within a timeout
- To authenticate users
- To create rooms
Answer: To detect dead connections when no pong returns within a timeout. Clients ping periodically; if no pong arrives within the timeout, the connection is treated as dead.
Do WebSockets guarantee message delivery on their own?
- Yes, always
- Only over wss://
- Only for JSON messages
- No — for critical messages you implement ACKs and retries
Answer: No — for critical messages you implement ACKs and retries. WebSockets do NOT guarantee delivery, so reliable messaging adds acknowledgments (ACKs) and retry logic.
What is the role of rooms/channels in the lesson?
- To encrypt messages
- To allow targeted messaging to a group instead of broadcasting to everyone
- To reconnect faster
- To store messages on disk
Answer: To allow targeted messaging to a group instead of broadcasting to everyone. Rooms group users so messages can be sent to a targeted set instead of every connected client.
How do you scale WebSockets across multiple servers in the lesson?
- Using a single giant server
- By disabling heartbeats
- Using a Pub/Sub message bus (like Redis) to broadcast across servers
- By switching to HTTP polling
Answer: Using a Pub/Sub message bus (like Redis) to broadcast across servers. A Pub/Sub pattern (Redis/NATS/Kafka) lets a message published on one server reach clients connected to all servers.
Continue this course
- Previous: Client-Side Security Essentials (XSS, CSRF, Sanitisation)
- Next: JavaScript Performance Optimization Techniques — Identify and fix slow JS with profiling, lazy loading, and code splitting
- Quick reference: JavaScript cheat sheet