designpattern.site

Observer Pattern

Define a one-to-many dependency so that when one object changes state, all its dependents are notified and updated automatically.

Overview

Most non-trivial applications have components that need to react when something else changes. A user's account balance updates and the UI should reflect the new value. A sensor reading changes and three different dashboards should refresh. A stock price moves and every subscribed trader's alert system should fire.

The Observer pattern is a behavioral design pattern that formalizes this relationship. One object — the Subject (also called Publisher or Observable) — maintains a list of dependents called Observers (or Subscribers). When the Subject's state changes, it notifies all registered Observers automatically, without knowing anything specific about them.

This decoupling is the pattern's core value. The Subject does not import or call the Observers directly. It only knows that they implement a common interface. Adding new types of Observers, or removing existing ones, requires zero changes to the Subject.

Problem & Motivation

Imagine you are building a stock trading dashboard. The StockTicker class holds a live price for each stock symbol. You have three views that need to stay in sync: a price table, a candlestick chart, and an alert panel that pings traders when a threshold is crossed.

Without Observer, StockTicker would need to know about every view and call each one directly after every price update:

// Tightly coupled — every new view requires editing StockTicker
ticker.updatePriceTable();
ticker.updateCandlestickChart();
ticker.checkAlertPanel();

This is brittle. Every new consumer forces changes to StockTicker. Every removed consumer leaves dead code. The views and the ticker are coupled so tightly that neither can evolve independently.

Observer solves this by inverting the dependency. Views register themselves with the ticker. The ticker just calls notify() and each registered view handles its own update logic. The ticker has no idea how many views exist or what they do.

Class Diagram

Loading diagram...

The key structural points are:

  • Subject defines the subscription interface: subscribe, unsubscribe, and notify.
  • ConcreteSubject holds the list of observers and the state that drives notifications.
  • Observer is an interface with a single update method — the only contract Observers must fulfill.
  • ConcreteObserverA and ConcreteObserverB implement Observer independently. Neither knows about the other.

Implementation

The implementations below model a StockTicker that tracks a stock's price. Three observers — a PriceDisplay, a PriceAlertSystem, and a TradeLogger — react independently whenever the price changes.

interface Observer {
  update(symbol: string, price: number): void;
}

class StockTicker {
  private observers: Observer[] = [];
  private prices: Map<string, number> = new Map();

  subscribe(observer: Observer): void {
    this.observers.push(observer);
  }

  unsubscribe(observer: Observer): void {
    this.observers = this.observers.filter((o) => o !== observer);
  }

  setPrice(symbol: string, price: number): void {
    this.prices.set(symbol, price);
    this.notify(symbol, price);
  }

  private notify(symbol: string, price: number): void {
    for (const observer of this.observers) {
      observer.update(symbol, price);
    }
  }
}

class PriceDisplay implements Observer {
  update(symbol: string, price: number): void {
    console.log(`[Display] ${symbol}: $${price.toFixed(2)}`);
  }
}

class PriceAlertSystem implements Observer {
  constructor(private threshold: number) {}

  update(symbol: string, price: number): void {
    if (price > this.threshold) {
      console.log(`[Alert] ${symbol} exceeded $${this.threshold}! Now: $${price.toFixed(2)}`);
    }
  }
}

class TradeLogger implements Observer {
  private log: string[] = [];

  update(symbol: string, price: number): void {
    const entry = `${new Date().toISOString()} ${symbol} @ $${price.toFixed(2)}`;
    this.log.push(entry);
    console.log(`[Logger] Recorded: ${entry}`);
  }

  getLog(): string[] {
    return [...this.log];
  }
}

// Usage
const ticker = new StockTicker();
const display = new PriceDisplay();
const alert = new PriceAlertSystem(150);
const logger = new TradeLogger();

ticker.subscribe(display);
ticker.subscribe(alert);
ticker.subscribe(logger);

ticker.setPrice("AAPL", 145.30);
ticker.setPrice("AAPL", 152.80); // triggers alert

ticker.unsubscribe(display);
ticker.setPrice("AAPL", 148.00); // display no longer notified

Push vs. Pull Notification

The examples above use a push model: the Subject passes the new state directly to each Observer's update method. An alternative is the pull model, where update receives only the Subject itself (or no arguments), and the Observer calls back to read the specific fields it cares about. Pull gives Observers more control and avoids sending data they do not need, but it requires Observers to hold a reference to the Subject, which can increase coupling. For simple, focused events like a price update, push is usually cleaner.

Real-World Examples

DOM event listeners — The browser's event system is Observer in disguise. addEventListener("click", handler) registers an Observer. The DOM node (Subject) fires the event to every registered listener. The node knows nothing about what its listeners do — they could update the UI, send analytics, or validate a form.

Reactive streams (RxJS, Kotlin Flow, Java Reactor) — Reactive libraries formalize and extend Observer into a full asynchronous programming model. An Observable is the Subject; subscribe() registers Observers. Operators like map, filter, and debounce sit between Subject and Observer, transforming the notification stream without either side knowing.

Message brokers (Kafka, RabbitMQ, Redis Pub/Sub) — At the infrastructure level, a message topic is a Subject. Producer services publish messages (call notify). Consumer services subscribe and process messages independently. Adding a new consumer does not require any change to the producer — the same fundamental contract as Observer, applied across process and network boundaries.

Pros and Cons

Advantages

Open/Closed Principle — you can add new Observer types without touching the Subject. The Subject is open for extension (new subscribers) and closed for modification.

Loose coupling — the Subject depends only on the Observer interface, not on any concrete class. Observers can be swapped, mocked, or composed freely.

Dynamic subscriptions — Observers can subscribe and unsubscribe at runtime, allowing the notification graph to adapt to application state (e.g., show/hide a panel, enable/disable an alert).

Broadcast communication — a single state change propagates to any number of interested parties with one notify() call, eliminating repetitive manual update calls scattered across the codebase.

Disadvantages

Unexpected update order — Observers are notified in subscription order by default. If one Observer's reaction affects state that another Observer reads, subtle ordering bugs arise. The pattern provides no built-in way to express "notify B after A has finished."

Memory leaks from forgotten unsubscription — if an Observer holds a reference to the Subject and is never unsubscribed, both objects remain alive. This is a common source of memory leaks in long-lived applications. JavaScript's WeakRef-based event buses or explicit cleanup hooks (React's useEffect teardown) address this.

Cascade effects — a single setState call can trigger a chain of notifications across many Observers, each of which may trigger further state changes. Tracing the root cause of a bug in a deep notification chain is difficult.

No return value from Observers — the Subject fires and forgets. If you need acknowledgment, error handling, or back-pressure, Observer alone is not enough — reach for a richer event or reactive framework instead.

When to Use / When to Avoid

Use Observer when:

  • Multiple parts of a system need to react to the same state change but should not be coupled to each other.
  • The set of interested parties changes at runtime (dynamic subscriptions and unsubscriptions).
  • You want to separate the logic that produces data from the logic that consumes it.
  • You are implementing an event system, a reactive data layer, or a pub/sub mechanism.

Avoid Observer when:

  • The notification chain is short and fixed — direct method calls are simpler and easier to trace.
  • Strong ordering guarantees between Observers are required — the pattern does not enforce ordering.
  • The update logic is synchronous and performance-critical — iterating a list of observers and dispatching adds overhead that compounds at high notification frequency.
  • Observers need to communicate results back to the Subject — consider a different pattern such as Command or a callback-based approach.

Mediator — both patterns reduce direct dependencies, but in opposite ways. Observer lets objects broadcast to many unknown listeners. Mediator introduces a central hub that routes messages between objects that know about each other but route everything through the hub. Use Mediator when the communication logic itself is complex enough to warrant its own class.

Event Aggregator — a practical evolution of Observer used in larger systems. Instead of each Subject managing its own list, a shared event bus (the aggregator) handles all subscriptions. Subjects publish to the bus; Observers subscribe from the bus. This removes even the weak coupling of Observers holding a reference to a specific Subject.

Command — where Observer is about reacting to state changes, Command is about encapsulating intent. They are often used together: an Observer receives a notification and creates a Command object to represent the required action, deferring or queuing its execution.

Strategy — an Observer's update method is essentially a strategy for responding to a specific event. If the behavior of an Observer is expected to vary significantly between use cases, consider extracting it into a Strategy object that the Observer delegates to.

Design Tip

In modern application frameworks — React, Vue, Angular, SwiftUI — Observer is baked into the data layer: React's state model, Vue's reactivity system, and SwiftUI's @Published are all implementations of this pattern. Before hand-rolling an Observer mechanism, check whether your framework already provides one. Build on the platform's reactive primitives rather than adding a competing notification layer that the framework is unaware of.

On this page