The idea
What it is
Observer solves a problem every interactive program has: one object changes, and a bunch of other objects need to react. Its intent in one sentence: let many dependents (observers) subscribe to one subject, and have the subject notify all of them automatically whenever its state changes. The subject keeps a list of who's interested; when something happens, it walks that list and calls each one. It never hard-codes who is listening β observers add and remove themselves at runtime.
The perfect mental model is a YouTube channel. You hit the π Subscribe button β that's you adding yourself to the channel's subscriber list. When the channel uploads a video, YouTube notifies every current subscriber, automatically. The channel doesn't know your name, doesn't care what device you're on, and never checks in with you individually β it just walks its list. Hit Unsubscribe and the notifications stop, because you're off the list. And crucially, you never have to refresh the channel page every minute asking "is there a new video yet?" β the news comes to you, exactly when it happens.
The one sentence to remember
Observer defines a one-to-many dependency: one subject, many observers. Observers subscribe() to the subject; when the subject's state changes it calls notify(), which walks the list and calls each observer's update(...) β the subject only ever knows the tiny Observer interface, never the concrete classes behind it.
You've already used it today
Every button.addEventListener("click", handler) you've ever written is the Observer pattern β the button is the subject, your handler is an observer on its list, and the click is the state change that triggers notify(). React re-rendering the UI when state changes, RxJS streams, and spreadsheet cells recalculating when another cell changes are all the same idea.
Mechanics
How it works
The two roles
Observer has exactly two participants, connected by one tiny interface:
- Subject (the channel) β owns the state everyone cares about and a private list of observers. It exposes three doors:
subscribe(observer)adds to the list,unsubscribe(observer)removes from it, andnotify()β usually called internally right after state changes β walks the list and calls each observer. - Observer (the subscriber) β anyone who implements one small interface with a single method:
update(...). That method is literally the subject calling you back. Concrete observers (a viewer, a UI panel, a logger) each do their own thing inside it. - The one-to-many trick β the subject stores observers by interface only. It can notify a viewer, a chart, and an email sender without knowing any of those classes exist. New observer types plug in without a single line changing in the subject.
interface Subscriber {
update(video: string): void; // the channel calls THIS
}
class Channel {
private subscribers: Subscriber[] = []; // the one-to-many list
subscribe(s: Subscriber) { this.subscribers.push(s); } // π
unsubscribe(s: Subscriber) { this.subscribers = this.subscribers.filter(x => x !== s); } // π
upload(title: string) {
// 1. the subject's state changes...
for (const s of this.subscribers) { // 2. ...so it walks its list
s.update(title); // 3. and pushes the news to each one
}
}
}That for loop inside upload() is the entire engine. The channel never asks who is on the list or what they'll do with the news β it fires update() at each entry and moves on. Subscribe four viewers and the loop runs four times; unsubscribe three of them and the very next upload notifies exactly one. The set of listeners is a runtime decision, not a compile-time one.
No more polling
The alternative to Observer is polling: every dependent repeatedly asks the subject "changed yet? changed yet?" on a timer. That's how you'd check a channel with no subscribe button β reload the page every minute. Polling wastes work when nothing changed and reacts late when something did (up to one full interval late). Observer inverts the direction: dependents register once, then the subject pushes at the exact moment of change. Zero wasted checks, zero built-in lag.
Push vs pull: what does update() carry?
- Push style β the subject sends the data along:
update(video). The observer gets everything it needs in the call. Simple and fast, but the subject has to guess what every observer wants, and may end up shipping data most of them ignore. - Pull style β the subject sends only "something changed", typically passing itself:
update(this). Each observer then reads exactly the state it cares about off the subject. More flexible when observers need different slices of state, at the cost of an extra round trip and a tighter grip on the subject.
The lapsed listener β Observer's classic memory leak
The subject's list holds a reference to every observer. Forget to unsubscribe() when an observer dies (a closed window, an unmounted component) and two bad things happen: the garbage collector can't reclaim it (the subject still points at it), and it keeps receiving updates as a ghost, often crashing on state it no longer has. Always pair every subscribe with an unsubscribe in teardown β component unmount, destructor, removeEventListener.
Order and reentrancy
The pattern makes no promise about notification order β don't write observers that only work if they run before some other observer. And be careful with reentrancy: an update() that mutates the subject or subscribes/unsubscribes mid-notification can corrupt the very list being walked (many implementations iterate over a copy for exactly this reason). Keep update() handlers small and side-effect-light.
Where you meet it in the wild
- DOM events β
addEventListener/removeEventListenerare subscribe/unsubscribe on a subject (the element). - React and friends β state changes notify the framework, which re-renders the dependent UI. Modelβview data binding in MVC/MVVM is the original GoF motivation.
- RxJS / reactive streams β Observables generalise the pattern into composable event pipelines.
- Webhooks β the same idea across the network: you register a URL with a service (subscribe), it POSTs to you when something happens (notify).
Observer vs pub/sub β close cousins, not twins
In Observer, the subject holds the list itself and calls its observers directly β the two sides share at least the update() interface and the observer knows which subject it signed up with. Pub/sub adds a broker in the middle (an event bus or message queue): publishers emit to the broker, subscribers register with the broker, and the two sides never know each other exists β not even by interface β and can even live in different processes. If you find yourself building a middleman object whose whole job is deciding who talks to whom, you're heading toward [[mediator]].
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation β no setup, no install. Play with it as you read.
About this simulation
A YouTube channel with four viewers. Hit π Subscribe on any viewer β the card turns green and the channel's subscribers counter ticks up: they're on the list now. Click βΆ Upload video and notification dots fan out along the wires to only the subscribed viewers β their π₯ inbox badges tick +1 while unsubscribed viewers stay dim, visibly skipped. Now flip a couple of πs and upload again: a different set gets notified, because the channel never knew who its viewers were β it just walks whatever its list says right now. That's the whole pattern: subscribe, get pushed to, unsubscribe, silence.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Build the subscriber list
Hit the π Subscribe chip on Ana and Chloe. Their cards turn green and the channel's counter reads subscribers: 2 β that's two subscribe() calls landing in the channel's list. Note that nothing has been notified yet: subscribing just registers interest; only a state change triggers the push.
Upload and watch the push
Click βΆ Upload video. The channel card pulses, then notification dots travel along the wires to only Ana and Chloe β their π₯ badges tick to 1 while Ben and Dan stay dim, visibly skipped. The explain panel reads 'Notified 2 of 4'. That's notify() walking the list: no polling, no asking β the channel pushes to whoever is registered.
Change the set at runtime
Flip Chloe to π Unsubscribe and turn π on for Ben and Dan, then upload again. This time the dots reach Ana, Ben and Dan β a different set than before, with the π₯ badges keeping the history (Ana 2, Chloe stuck at 1). The channel's code never changed; only its list did. Hit βΊ Reset to start clean.
In practice
When to use it β and what trips people up
Reach for Observer when...
- One object's changes must reach a varying set of dependents β you can't hard-code the receivers because they come and go at runtime (viewers subscribing, panels opening and closing, plugins registering).
- You're building event-driven UI β clicks, keystrokes, model-changed-so-redraw-the-view. Virtually every UI framework is Observer at its core.
- You want the producer decoupled from its consumers β the subject ships one
notify(), and brand-new consumer types plug in later without touching the subject's code.
Skip it when...
- There's exactly one fixed, known dependent β just call it directly. A subscription list for a single permanent listener is ceremony with no payoff.
- Update cascades are getting hard to follow β when observers mutate state that notifies other observers, control flow turns into invisible spaghetti. Centralise the choreography in a [[mediator]] or an explicit event bus instead.
- Strict ordering or transactional delivery matters β plain Observer promises neither; if receivers must run in a fixed order or all-or-nothing, you need a more explicit orchestration.
What it gives you
- Loose coupling β the subject depends on one tiny interface, never on concrete observer classes, so either side can change freely.
- Open for extension β add a brand-new kind of observer (logger, chart, emailer) without touching one line of the subject.
- Runtime dynamism β the set of listeners is data, not code: subscribe and unsubscribe freely while the program runs.
- Push replaces polling β dependents are told at the exact moment of change instead of wastefully asking on a timer.
Common mistakes
- Lapsed-listener leaks β a forgotten unsubscribe keeps dead observers referenced (never garbage-collected) and receiving ghost updates.
- Unspecified order β observers are notified in whatever order the list happens to have; code that relies on order breaks silently.
- Hidden cascades β an update() that changes other subjects can trigger chains of notifications nobody planned or can easily trace.
- Harder debugging β the caller of update() is a loop inside the subject, so 'who made this happen?' isn't visible by reading the observer's code.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// The Observer interface β the ONLY thing the channel knows about viewers.
interface Subscriber {
update(video: string): void; // the channel calls this
}
// The Subject β owns state + the list, and pushes on change.
class Channel {
private subscribers: Subscriber[] = []; // one-to-many list
subscribe(s: Subscriber): void {
this.subscribers.push(s); // π join the list
}
unsubscribe(s: Subscriber): void { // π leave the list
this.subscribers = this.subscribers.filter(x => x !== s);
}
upload(title: string): void {
console.log(`πΊ uploaded "${title}"`);
// state changed β walk the list and PUSH (nobody polls)
for (const s of [...this.subscribers]) s.update(title);
}
}
// A concrete Observer β the channel never sees this class name.
class Viewer implements Subscriber {
constructor(private name: string) {}
update(video: string): void { // push style: data arrives
console.log(`${this.name} π new video: ${video}`);
}
}
const channel = new Channel();
const ana = new Viewer("Ana");
const ben = new Viewer("Ben");
channel.subscribe(ana);
channel.subscribe(ben);
channel.upload("Observer explained"); // β Ana AND Ben notified
channel.unsubscribe(ben); // Ben leaves the list...
channel.upload("Strategy explained"); // β only Ana notifiedReferences & further reading
5 sources- Articlerefactoring.guru
Observer β Refactoring.Guru
The clearest illustrated walkthrough: publisher/subscriber roles, structure diagram, real-world analogy, and code in many languages. Best first read.
- Docsen.wikipedia.org
Observer pattern β Wikipedia
Concise reference with the UML structure, push vs pull variants, the coupling and lapsed-listener criticisms, and language-level implementations.
- Booken.wikipedia.org
Design Patterns: Elements of Reusable Object-Oriented Software β Gamma, Helm, Johnson, Vlissides
The original 'Gang of Four' book. Observer is one of its most influential entries β the formal intent, the push/pull discussion, and the MVC motivation come from here.
- Docsdeveloper.mozilla.org
EventTarget.addEventListener() β MDN
The Observer pattern you use every day: the DOM's subscribe call, with removeEventListener as the unsubscribe that prevents lapsed listeners.
- Docsrxjs.dev
RxJS β Observables overview
Where Observer grows up: Observables generalise subject/subscriber into composable, cancellable event streams used across modern front-end code.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Sign in to save your best score.
question 01 / 06
What is the intent of the Observer pattern?
question 02 / 06
What does the subject (the channel) actually know about its observers?
question 03 / 06
What's the difference between push-style and pull-style notification?
question 04 / 06
You destroy a UI panel but forget to unsubscribe it from the model it was observing. What happens?
question 05 / 06
How does classic Observer differ from pub/sub?
question 06 / 06
Before adding Observer, a dashboard asked the stock service for the latest price every second. What did the pattern change?
0/6 answered