Observer
Define a subscription mechanism so multiple objects are automatically notified when the object they're watching changes state. The foundation of event systems, reactive UIs, and pub/sub architectures.
The Problem
You’re building a weather app. The WeatherData object collects temperature readings. Three displays — CurrentConditionsDisplay, StatisticsDisplay, and ForecastDisplay — need to update whenever the data changes.
Option A: poll each display — inefficient, introduces lag.
Option B: hardcode currentDisplay.update() and statsDisplay.update() inside WeatherData — WeatherData now couples to every display type. Adding a new display means modifying WeatherData.
Neither scales.
The Solution
The Publisher (subject) maintains a list of Subscribers (observers). When something interesting happens, it notifies all subscribers via a standard update() method. Subscribers can subscribe or unsubscribe at any time. The publisher doesn’t know anything about the concrete subscriber types.
Real-World Analogy
A magazine subscription. The publisher prints each issue and mails it to every subscriber on their list. Subscribers can join or cancel at any time. The publisher doesn’t deliver to non-subscribers, and doesn’t need to know what subscribers do with the magazine. New subscribers don’t require a new magazine category — they just sign up.
TypeScript Example
// Observer/Subscriber interface
interface WeatherObserver {
update(temperature: number, humidity: number, pressure: number): void;
}
// Subject/Publisher
class WeatherStation {
private observers: WeatherObserver[] = [];
private temperature = 0;
private humidity = 0;
private pressure = 0;
subscribe(observer: WeatherObserver): void {
if (!this.observers.includes(observer)) {
this.observers.push(observer);
}
}
unsubscribe(observer: WeatherObserver): void {
this.observers = this.observers.filter(o => o !== observer);
}
private notify(): void {
for (const observer of this.observers) {
observer.update(this.temperature, this.humidity, this.pressure);
}
}
// When measurements change, notify all observers
setMeasurements(temp: number, humidity: number, pressure: number): void {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
console.log(`\n[WeatherStation] New readings: ${temp}°C, ${humidity}% RH, ${pressure} hPa`);
this.notify();
}
}
// Concrete observers
class CurrentConditionsDisplay implements WeatherObserver {
update(temperature: number, humidity: number): void {
console.log(` [CurrentConditions] ${temperature}°C, ${humidity}% humidity`);
}
}
class StatisticsDisplay implements WeatherObserver {
private readings: number[] = [];
update(temperature: number): void {
this.readings.push(temperature);
const avg = this.readings.reduce((s, t) => s + t, 0) / this.readings.length;
const max = Math.max(...this.readings);
const min = Math.min(...this.readings);
console.log(` [Statistics] avg=${avg.toFixed(1)}°C, min=${min}°C, max=${max}°C`);
}
}
class AlertSystem implements WeatherObserver {
update(temperature: number, humidity: number, pressure: number): void {
if (temperature > 35) console.log(` [ALERT] Heat warning: ${temperature}°C`);
if (pressure < 1000) console.log(` [ALERT] Low pressure storm risk: ${pressure} hPa`);
}
}
// Usage
const station = new WeatherStation();
const current = new CurrentConditionsDisplay();
const stats = new StatisticsDisplay();
const alertSystem = new AlertSystem();
station.subscribe(current);
station.subscribe(stats);
station.subscribe(alertSystem);
station.setMeasurements(22, 65, 1013);
station.setMeasurements(28, 70, 1010);
station.setMeasurements(37, 80, 998); // triggers heat + low pressure alerts
// Dynamic subscription — unsubscribe mid-way
console.log('\n[Unsubscribing current conditions display]');
station.unsubscribe(current);
station.setMeasurements(25, 60, 1015);
// → current display no longer receives updates
Built-in Observer in Node.js
import { EventEmitter } from 'events';
class OrderService extends EventEmitter {
placeOrder(orderId: string, amount: number): void {
console.log(`[OrderService] Order ${orderId} placed`);
this.emit('order:placed', { orderId, amount });
}
}
const orders = new OrderService();
orders.on('order:placed', ({ orderId }) => console.log(`[Email] Confirmation sent for ${orderId}`));
orders.on('order:placed', ({ orderId, amount }) => console.log(`[Analytics] $${amount} revenue from ${orderId}`));
orders.placeOrder('ORD-001', 99.99);
When to Use
- State change in one object requires changing others, and you don’t know how many ahead of time
- An object should be able to notify others without knowing who they are
- You’re building event systems, reactive state, or pub/sub messaging
- Examples: UI event listeners, DOM events, Redux store, RxJS observables, message queues, webhooks
Pros
- Open/Closed — add new subscribers without changing the publisher
- Establish relationships between objects at runtime
- Publishers are loosely coupled from subscriber implementations
Cons
- Subscribers are notified in an unpredictable order (order depends on when they subscribed)
- If not managed carefully, publishers can hold references to removed subscribers (memory leaks)
- Complex chains of notifications can cause hard-to-trace cascades
Related Patterns
- Mediator centralises communication between components; Observer broadcasts to all subscribers
- Singleton — the publisher or event bus is often a Singleton
- Command — observer callbacks can be wrapped as Command objects for undoable event handling
- Iterator can traverse subscriber lists