WebSockets Cheatsheet

Broadcasting

Use this WebSockets reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Broadcast to All Connected Clients

// ws library — wss.clients is a Set<WebSocket>
function broadcast(wss, data) {
  for (const client of wss.clients) {
    if (client.readyState === WebSocket.OPEN) {
      client.send(data);
    }
  }
}

wss.on("connection", (ws) => {
  ws.on("message", (msg) => broadcast(wss, msg));
});

Broadcast to All Except Sender

function broadcastExcept(wss, sender, data) {
  for (const client of wss.clients) {
    if (client !== sender && client.readyState === WebSocket.OPEN) {
      client.send(data);
    }
  }
}

wss.on("connection", (ws) => {
  ws.on("message", (msg) => broadcastExcept(wss, ws, msg));
});

Typed Broadcast with JSON Envelope

function broadcastJSON(wss, type, payload, exclude = null) {
  const frame = JSON.stringify({ type, payload, ts: Date.now() });
  for (const client of wss.clients) {
    if (client !== exclude && client.readyState === WebSocket.OPEN) {
      client.send(frame);
    }
  }
}

// Usage
wss.on("connection", (ws) => {
  broadcastJSON(wss, "join", { count: wss.clients.size });

  ws.on("message", (raw) => {
    const { type, payload } = JSON.parse(raw.toString());
    broadcastJSON(wss, type, payload, ws);   // exclude sender
  });

  ws.on("close", () => {
    broadcastJSON(wss, "leave", { count: wss.clients.size });
  });
});

Targeted Send (Unicast)

// Tag each socket with a userId on connect
wss.on("connection", (ws, req) => {
  const params = new URL(req.url, "http://x").searchParams;
  ws.userId = params.get("userId");
});

function sendToUser(wss, userId, data) {
  for (const client of wss.clients) {
    if (client.userId === userId && client.readyState === WebSocket.OPEN) {
      client.send(data);
      break;
    }
  }
}

Managing a Custom Client Map

// Map for O(1) user lookups
const clients = new Map(); // userId → Set<WebSocket> (multiple tabs)

wss.on("connection", (ws, req) => {
  const userId = authenticate(req);
  if (!clients.has(userId)) clients.set(userId, new Set());
  clients.get(userId).add(ws);

  ws.on("close", () => {
    clients.get(userId)?.delete(ws);
    if (clients.get(userId)?.size === 0) clients.delete(userId);
  });
});

function sendToUser(userId, data) {
  const sockets = clients.get(userId);
  if (!sockets) return;
  for (const ws of sockets) {
    if (ws.readyState === WebSocket.OPEN) ws.send(data);
  }
}

Broadcast Patterns Summary

PatternRecipientCode pattern
Broadcast allEvery connected clientfor (client of wss.clients)
Broadcast except senderEveryone but the senderclient !== ws guard
UnicastOne specific userLook up by ws.userId
Multicast / roomA subsetCustom Set or Map per group
Tagged broadcastClients matching a predicateFilter in loop

Efficient Binary Broadcast (avoid re-serializing)

// Serialize once, send the same Buffer to all clients
function broadcastBinary(wss, buffer) {
  const data = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
  for (const client of wss.clients) {
    if (client.readyState === WebSocket.OPEN) {
      client.send(data, { binary: true });
    }
  }
}

Sending the same Buffer reference to multiple clients is safe — ws does not mutate the buffer.

Presence: Track Online Users

const onlineUsers = new Set();

wss.on("connection", (ws, req) => {
  const userId = getUserId(req);
  ws.userId = userId;
  onlineUsers.add(userId);

  broadcastJSON(wss, "presence", { online: [...onlineUsers] });

  ws.on("close", () => {
    onlineUsers.delete(userId);
    broadcastJSON(wss, "presence", { online: [...onlineUsers] });
  });
});

Rate-Limiting Broadcast

const sendTimes = new Map(); // ws → last send timestamp

function rateLimitedBroadcast(ws, wss, data) {
  const now = Date.now();
  const last = sendTimes.get(ws) ?? 0;
  if (now - last < 100) return; // max 10 msgs/sec per client
  sendTimes.set(ws, now);
  broadcastExcept(wss, ws, data);
}

Gotchas

  • wss.clients includes sockets in CLOSING state — always check readyState === WebSocket.OPEN.
  • wss.clients is a Set, not an array — use for...of, not .forEach() index access.
  • For high-frequency broadcasts (game loops), serialize the message once outside the loop rather than inside.
  • Iterating wss.clients while clients connect/disconnect is safe, but not because of anything ws does — it is a plain Set, and JS Set iterators tolerate mutation (deleted entries are skipped; entries added mid-iteration are visited).
  • For multi-process or multi-server deployments, wss.clients only contains clients connected to this process. Use Redis pub/sub to fan out across nodes (see Scaling page).