Lesson 73 of 80
One object announces 'something happened' without knowing or caring who's listening. It's the pattern under DOM events, RxJS, Redux subscriptions, and every pub/sub library you've ever used.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The Observer pattern is one object (the subject) keeping a list of interested parties (observers) and notifying all of them when something happens, without knowing anything about who those observers are or what they'll do with the news. The subject just says "this happened" — it never calls a specific observer's specific method by name, and it doesn't hold a permanent, tight reference to any one of them beyond the list.
This is also called pub/sub (publish/subscribe) when the subject and observers are decoupled through an intermediary rather than talking directly — same core idea, slightly different shape. addEventListener, Node's EventEmitter, Redux's store.subscribe, and RxJS observables are all this pattern wearing different clothes.
class EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) this.#listeners.set(event, new Set());
this.#listeners.get(event).add(callback);
return () => this.#listeners.get(event).delete(callback); // unsubscribe
}
emit(event, payload) {
this.#listeners.get(event)?.forEach((callback) => callback(payload));
}
}
const cart = new EventEmitter();
const unsubscribe = cart.on("item-added", (item) => {
console.log(`Added: ${item.name}`);
});
cart.on("item-added", (item) => {
updateCartBadge(item);
});
cart.emit("item-added", { name: "Keyboard" });
// both listeners run — cart doesn't know or care what either one does
unsubscribe(); // the logging listener stops receiving events
cart never references updateCartBadge or the logging function by name anywhere in its own definition — it only knows "call whatever's registered for item-added." That's the whole point: you can add a third, fourth, tenth listener later, from completely unrelated code, without ever touching EventEmitter itself. The on call returning an unsubscribe function is the other half of a correct implementation — without a way to stop listening, every subscriber leaks for the lifetime of the emitter.
emit is one-directional — the subject broadcasts outward. If an observer needs to talk back to the subject, that's a separate, ordinary function call, not part of this pattern.emit calls every listener synchronously, one after another, in registration order. A slow or throwing listener blocks (or breaks) every listener registered after it — production event emitters usually wrap each call in a try/catch specifically because of this.The #listeners map is private state closed over by on and emit — the same closure mechanism from the closures lesson, just used to hold a collection of callbacks instead of one counter or timer. And every callback stored in it is itself an ordinary first-class function being passed around, exactly like debounce's inner function — the observer pattern doesn't need anything the language hasn't already given you.