1. Pub/sub: the heart
Underneath "send a message and have it reach everyone" there's a classic pattern: publish / subscribe (pub/sub).
- Interested parties subscribe by giving a function (a callback).
- When someone publishes a message, all the subscribed functions are called with that message.
The publisher doesn't know who is listening; it just emits. That decouples the sender and the receivers.
const subs = [];
function subscribe(fn) { subs.push(fn); }
function send(msg) { subs.forEach((fn) => fn(msg)); }
2. Rooms
A chat isn't a single channel: it has rooms. A room groups the people
that should receive the same messages ("general", "random", a DM...). In
Socket.io it's socket.join("general") and io.to("general").emit(...).
Conceptually, a room is a pub/sub with its own list of subscribers: publishing to "general" only notifies those who are subscribed to "general".
3. Presence (who is connected)
Presence answers "who is here right now?". When someone
connects, they're added to the list; when they leave (or the connection drops), they're
removed. A Set is perfect: it avoids duplicates if you reconnect and lets you
ask "is so-and-so here?" in O(1).
const connected = new Set();
connected.add("ana"); // enters
connected.delete("ana"); // leaves
[...connected]; // current list
With these three pieces —pub/sub, rooms and presence— you have the complete mental model of a real-time chat. Let's build them.
Examples
Minimal pub/sub in action
const subs = [];
const subscribe = (fn) => subs.push(fn);
const send = (m) => subs.forEach((fn) => fn(m));
subscribe((m) => console.log("A receives:", m));
subscribe((m) => console.log("B receives:", m));
send("hi");