Observer Pattern β Pub/Sub, Event Listeners (2026)
In this tutorial, you'll learn how the observer pattern enables one-to-many communication between objects, how publish-subscribe systems decouple event producers from consumers, and how modern reactive frameworks use this pattern under the hood.
A YouTube channel is the perfect analogy. You subscribe to a creator. When they upload a new video, every subscriber gets notified automatically. The creator doesn't personally message each subscriber β they just publish to their channel. The platform handles distribution. If you unsubscribe, you stop getting notifications. The creator never needs to know who subscribed or unsubscribed. That's the observer pattern: a subject (channel) notifies observers (subscribers) without knowing anything about them.
Core Concept
The observer pattern defines a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified automatically. This is also known as publish-subscribe (pub/sub) or the event-listener pattern.
// The observer interface β anything that wants notifications
interface Observer {
update(event: string, data: unknown): void;
}
// The subject β manages observers and sends notifications
class EventBus {
private observers: Map<string, Observer[]> = new Map();
subscribe(event: string, observer: Observer): void {
if (!this.observers.has(event)) {
this.observers.set(event, []);
}
this.observers.get(event)!.push(observer);
}
unsubscribe(event: string, observer: Observer): void {
const list = this.observers.get(event);
if (!list) return;
const index = list.indexOf(observer);
if (index !== -1) list.splice(index, 1);
}
emit(event: string, data: unknown): void {
const list = this.observers.get(event);
if (!list) return;
list.forEach(observer => observer.update(event, data));
}
}
How It Works
The subject maintains a list of observers. Observers register themselves for specific events. When the subject's state changes, it iterates over the relevant observers and calls their update method. The subject doesn't know what observers do β it just notifies them.
βββββββββββββββ notify ββββββββββββββββ
β β βββββββββββββββ β Observer A β
β Subject β ββββββββββββββββ
β (EventBus) β notify ββββββββββββββββ
β β βββββββββββββββ β Observer B β
βββββββββββββββ ββββββββββββββββ
β
β subscribe / unsubscribe
β
ββββββββββββ
β Observersβ (any number)
ββββββββββββ
Browser Event Listener Example
The browser's DOM event system is a textbook observer pattern:
// The subject is the DOM element
const button = document.getElementById("submitBtn");
// Observers register via addEventListener
button.addEventListener("click", () => {
console.log("Button was clicked!");
});
button.addEventListener("click", () => {
console.log("Also logging the click");
});
// When the button is clicked, BOTH observers fire
// Output:
// Button was clicked!
// Also logging the click
Custom Event System
Here's a complete pub/sub implementation for an application event system:
class AppEventBus {
private handlers: Map<string, Set<Function>> = new Map();
on(event: string, handler: Function): void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
}
off(event: string, handler: Function): void {
this.handlers.get(event)?.delete(handler);
}
emit(event: string, ...args: unknown[]): void {
this.handlers.get(event)?.forEach(handler => {
handler(...args);
});
}
}
// Usage
const bus = new AppEventBus();
bus.on("user:login", (user) => {
console.log(`Welcome back, ${user.name}`);
});
bus.on("user:login", (user) => {
// Log security event
auditLogger.log(`User ${user.id} logged in`);
});
bus.emit("user:login", { id: 1, name: "Alice" });
// Output:
// Welcome back, Alice
// User 1 logged in
Expected output: The event bus notifies all registered handlers for the user:login event. Adding or removing handlers at runtime doesn't affect other handlers.
Real-World Examples
Reactive Programming (RxJS)
RxJS is built entirely on the observer pattern. Observables are subjects, and observers subscribe to receive values over time. Angular's HTTP client returns Observables β when data arrives, all subscribers are notified.
import { Observable } from "rxjs";
const dataStream = new Observable(subscriber => {
subscriber.next("First value");
subscriber.next("Second value");
subscriber.complete();
});
dataStream.subscribe(value => console.log(value));
// Output:
// First value
// Second value
Notification Systems
Doda Browser's extension system uses an internal event bus. When an extension triggers an action (download complete, tab closed, bookmark added), the event bus notifies all registered listeners. Extensions can subscribe to events without knowing about each other.
Pros & Cons
| Pros | Cons |
|---|---|
| Loose coupling between subject and observers | Memory leaks if observers aren't unregistered |
| Supports broadcast communication | Debugging is harder (unclear who receives events) |
| Observers can be added/removed at runtime | Order of notification isn't guaranteed |
| Natural fit for event-driven systems | Can cause cascading updates if not designed carefully |
When to Use
Use the observer pattern when:
- One object needs to notify many others without knowing who they are
- Changes to one object require updating related objects
- You're building event-driven or reactive systems
- You want loose coupling between components that need to communicate
Avoid it when the notification chain is simple enough for direct calls, or when every observer needs to be notified in a specific order.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro