AcademyDesign Patterns
Observer pattern
Notifying many listeners without the subject knowing who they are, the leak a forgotten unsubscribe causes silently, and why every event system pairs subscribe with teardown.
5 min readUpdated 3 exercises
Observer is the pattern for one-to-many notification: it exists so a subject can change without every interested party needing a direct line back into it.
An Observer lets a subject broadcast "something changed" to a list of listeners it holds no other opinion about. It doesn't know what they do with the notification, or how many there are, only that they asked to be told.
The problem it solves
An order is placed. Several things need to happen: send a confirmation email, update inventory, record analytics, refresh the admin dashboard.
The direct approach is for the order code to call each one:
async function placeOrder(order: Order) {
await save(order);
await sendEmail(order);
await updateInventory(order);
await trackAnalytics(order);
}- The order code knows everyone who cares. Adding a fifth consumer means editing
placeOrder. - One failure affects the rest. If analytics throws, the dashboard never updates.
- It cannot be reused. Any code that wants to react to an order has to be wired into this function.
The fix is for the order to announce "an order was placed" and let anything interested subscribe to that announcement.
Structure
Two parts:
- The subject: holds a list of observers, lets them subscribe and unsubscribe, and calls
notifywhen its state changes. - The observers: any function or object with the method the subject calls. Each decides for itself what to do.
class Subject {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
notify(data) {
for (const fn of this.observers) fn(data);
}
}The subject never learns who its observers are or what they do. Adding one is a subscribe call, and removing one changes nothing else.
Where it is used in real systems
| Use case | The subject and its observers |
|---|---|
| UI events | A DOM element and its click listeners. |
| State management | A store and the components subscribed to it. |
| Domain events | "Order placed" with email, inventory and analytics reacting. |
| Real-time updates | A WebSocket message delivered to every connected client. |
| Reactive data | A value and everything derived from it. |
| Webhooks | A service notifying every registered URL. |
| Logging and monitoring | Application events sent to several sinks. |
Node.js EventEmitter | The language-level version of this pattern. |
Observers you already use
- `addEventListener`. The DOM is the most widely used implementation of Observer.
- Node.js `EventEmitter`.
emitter.on('event', fn)andemitter.emit('event', data). - Redux and Zustand `subscribe`. Components subscribe to a store and re-render when it changes.
- RxJS Observables. Observer taken further, with operators to transform, filter and combine streams.
- Vue reactivity and Angular signals. Reading a value registers a dependency, and changing it notifies the readers.
- Webhooks. Clerk and Lemon Squeezy notify your endpoint when something happens on their side.
The details that cause bugs
Observer is easy to write and easy to get subtly wrong.
- Memory leaks. A subscriber that never unsubscribes stays in the list forever, along with everything it references. In components, unsubscribe on unmount.
subscribeshould return an unsubscribe function. - Order of notification. Do not rely on observers running in a particular order. If order matters, it is a different pattern.
- Errors in one observer. A thrown error in the middle of
notifycan skip the remaining observers. Decide whether to catch per observer. - Changing the list while notifying. An observer that unsubscribes itself during
notifycan cause another to be skipped. Loop over a copy. - Cascading updates. An observer that changes the subject triggers
notifyagain, and can loop forever. - Sync or async. Synchronous notification means a slow observer slows the subject. Queue work if needed.
A safer subject:
subscribe(fn: (data: string) => void): () => void {
this.observers.push(fn);
return () => {
this.observers = this.observers.filter((o) => o !== fn);
};
}Observer or Mediator
| Observer | Mediator | |
|---|---|---|
| Shape | One subject, many listeners | Many peers, one hub |
| Coupling | Subject knows there are listeners | Peers know only the mediator |
| Logic | In each listener | Centralized |
When not to use it
- One consumer. A direct call or a callback is clearer than a subscription system.
- You need the result. Observers do not return values to the subject. If the caller needs an answer, call the function directly.
- The flow is hard to follow. Events scattered across a codebase make it hard to answer "what happens when this fires?". Name events clearly, and keep the list small.
- Strict ordering or transactions. If step two must only run when step one succeeded, use an explicit sequence, not independent listeners.
What interviewers ask
- "What is the Observer pattern?" A subject that notifies a list of subscribers when its state changes, without knowing them.
- "Where is it used in JavaScript?" DOM events,
EventEmitter, Redux subscriptions, RxJS. - "How do you prevent memory leaks?" Return an unsubscribe function, and call it when the subscriber goes away.
- "Observer versus Pub/Sub?" In Pub/Sub a broker sits between publishers and subscribers, and they do not know each other. In Observer the subject holds its observers directly.
- "Observer versus Mediator?" Observer broadcasts from one subject. Mediator routes between peers.
The mistake to avoid
Subscribing without ever unsubscribing. Every subscribe(fn) that is not paired with a way out keeps fn, and whatever it captured, alive for as long as the subject lives. In a long-running server or a single-page app, that is a slow memory leak that only shows up after hours. Always keep the unsubscribe function and call it in cleanup.
The mechanism above, a list of callbacks the subject walks through when something happens, is the same in JavaScript, Python or Java. Only the syntax changes.
Now practice it
1 challenge · 2 quizzesReading this page is the cheap half. These are the exercises that make you use it.
- ObserverProWire a subject up to multiple subscribers and prove notify() reaches every one of them — the broadcast this page diagrams, made runnable.ChallengeSolve · EasyChallengeSolve · Easy
- Design Patterns · Behavioral PatternsTen quick questions across Strategy, State, Observer, Command and the rest of the behavioral family, to check the mental model before this one.QuizTake · HardQuizTake · Hard
- Design Patterns · Architectural Patterns — Series 2ProA second, tougher pass on the same behavioral family, for once the first set feels easy.QuizTake · HardQuizTake · Hard
Read next
Mediator pattern
Routing communication between many objects through one hub, the god object the hub itself can become, and the temptation to route every interaction through it "to be safe."
5 min
Chain of Responsibility pattern
Trying handlers one at a time until one accepts the request, the dead end when a request reaches the last link unhandled, and why every chain needs a terminal case.
7 min