The Observer pattern is a behavioral pattern: a subject holds state and notifies observers when that state changes. Observers subscribe; the subject pushes updates. That keeps producers of events decoupled from how consumers react.
In the browser, much of this is built in (addEventListener, EventTarget). You can also implement a tiny subject/observer API with arrays of callbacks—useful in Node or when you want full control.
For DOM listener lifecycle (remove listeners, AbortSignal), see JavaScript addEventListener.
Tested with Node.js v22 on Linux for the runnable examples below.
Observer with EventTarget
EventTarget is the same interface document and Element use for events. Subclass it to get addEventListener, removeEventListener, and dispatchEvent for application-level “change” notifications.
class Subject extends EventTarget {
constructor() {
super();
this.value = null;
}
setValue(newValue) {
this.value = newValue;
this.dispatchEvent(new Event("change"));
}
}
class Observer {
constructor(subject) {
this.subject = subject;
this.subject.addEventListener("change", this.update.bind(this));
}
update() {
console.log(`Value has been updated: ${this.subject.value}`);
}
}
const subject = new Subject();
const observer = new Observer(subject);
subject.setValue("new value");You should see one line logging Value has been updated: new value.
Observer with callbacks (manual subscribe list)
Here the subject keeps an array of callbacks and invokes them when the value changes. removeObserver drops a specific callback.
class Subject {
constructor() {
this.value = null;
this.observers = [];
}
setValue(newValue) {
this.value = newValue;
this.notifyObservers();
}
addObserver(callback) {
this.observers.push(callback);
}
removeObserver(callback) {
this.observers = this.observers.filter((o) => o !== callback);
}
notifyObservers() {
for (const observer of this.observers) {
observer(this.value);
}
}
}
const subject = new Subject();
const observer1 = (value) => {
console.log(`Observer 1 has been notified: ${value}`);
};
const observer2 = (value) => {
console.log(`Observer 2 has been notified: ${value}`);
};
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.setValue("new value");
subject.removeObserver(observer2);
subject.setValue("another value");You should see 3 lines, in order: Observer 1 has been notified: new value, Observer 2 has been notified: new value, Observer 1 has been notified: another value.
Observer vs Pub/Sub
| Topic | Observer | Pub/Sub |
|---|---|---|
| Wiring | Subject knows its observers (or holds direct references). | Publishers and subscribers are decoupled through a broker / event bus. |
| Coupling | Tighter between subject and observers. | Looser; participants only know the channel names. |
| Flexibility | Simple for one-to-many inside one module. | Better when many producers/consumers or cross-cutting topics matter. |
Choose Observer when a single object owns state and a small set of listeners is enough; choose Pub/Sub when you need a shared message layer.
Errors and observers
- Wrap observer bodies in
try/catchso one failing callback does not break the rest (or isolate each call in the subject’snotifyObservers). - Validate payloads before acting, or use a schema library if notifications carry structured data.
- Subjects can catch errors in
notifyObservers, log them, and optionally retry or skip bad subscribers.
Avoiding memory leaks
- Remove listeners when a component unmounts or an observer is done (
removeEventListener, orAbortSignalwithaddEventListener— see the addEventListener guide). - For callback lists, always provide
removeObserver(or return an unsubscribe function fromaddObserver). WeakMap/WeakSetcan help when observers are keyed by objects that should not be kept alive solely by the subject.- Libraries such as RxJS implement richer subscription and teardown semantics; learn their
unsubscribe/Subscriptionpatterns rather than treating streams as fire-and-forget.
Summary
You can implement the Observer pattern in JavaScript with EventTarget (browser-native semantics) or a small callback registry (works everywhere). Understand Observer vs Pub/Sub coupling, harden notify loops against errors, and tear down listeners or subscriptions to avoid leaks.
