🔄 Behavioral

Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Observer decouples the subject from its observers, enabling publish-subscribe communication without either side knowing the concrete type of the other.

Problem

When one object's state changes and other objects need to react, hardcoding these dependencies creates tight coupling. If a customer is interested in a product that's out of stock, they'd have to check the store every day. Most visits would be pointless. But the store can't send notifications to every customer for every product—that wastes resources for uninterested customers.

Solution

Define a subscription mechanism in the subject so that individual objects can subscribe to or unsubscribe from a stream of events. The subject maintains a list of observers and notifies them by calling a defined update method. Observers can attach or detach at any time, and the subject doesn't need to know anything about their concrete classes.

Participants

RoleResponsibility
SubjectKeeps a list of observers and provides methods to attach, detach, and notify them
ObserverDefines an updating interface for objects that should be notified of changes
ConcreteSubjectStores state of interest and sends notifications when its state changes
ConcreteObserverImplements the Observer interface to keep its state consistent with the subject's

TypeScript Example

TypeScript
type EventHandler<T> = (data: T) => void;

class EventBus<Events extends Record<string, unknown>> {
  private listeners = new Map<string, Set<EventHandler<any>>>();

  on<K extends keyof Events & string>(
    event: K,
    handler: EventHandler<Events[K]>,
  ): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(handler);
    return () => this.listeners.get(event)?.delete(handler);
  }

  emit<K extends keyof Events & string>(event: K, data: Events[K]) {
    this.listeners.get(event)?.forEach((fn) => fn(data));
  }
}

interface AppEvents {
  userLogin: { userId: string; timestamp: number };
  purchase: { item: string; price: number };
}

const bus = new EventBus<AppEvents>();

const unsub = bus.on("userLogin", (data) => {
  console.log(`Welcome ${data.userId} at ${data.timestamp}`);
});

bus.on("purchase", (data) => {
  console.log(`Purchased ${data.item} for $${data.price}`);
});

bus.emit("userLogin", { userId: "alice", timestamp: Date.now() });
bus.emit("purchase", { item: "Keyboard", price: 79.99 });
unsub();

Real-World Example

Event systems are everywhere: DOM event listeners in browsers, Node.js EventEmitter, React's state management (useState triggers re-renders of observing components), Redux store subscriptions, WebSocket message handlers, and database change streams. RSS feeds and newsletter subscriptions are real-world Observer implementations—subscribers receive updates when new content is published.

When to Use

  • Changes to one object require changing others, and you don't know how many objects need to change
  • An object should notify other objects without knowing who those objects are
  • You need to implement a distributed event-handling system
  • You want a publish-subscribe communication model

When Not to Use

  • The order of notification matters and is hard to control
  • Subscribers and subjects have a complex dependency that could lead to update cascades or infinite loops
  • The notification payload varies significantly for different subscribers, requiring them to do excessive filtering

Understanding the Observer Pattern

The Observer pattern is one of the most important behavioral design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Observer decouples the subject from its observers, enabling publish-subscribe communication without either side knowing the concrete type of the other. The pattern involves the following key participants: Subject, Observer, ConcreteSubject, ConcreteObserver. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.

In TypeScript and modern JavaScript development, the Observer pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Observer implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.

Knowing when to apply the Observer pattern is as important as knowing how. Common use cases include: Changes to one object require changing others, and you don't know how many objects need to change; An object should notify other objects without knowing who those objects are; You need to implement a distributed event-handling system. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.

The Observer pattern is related to several other design patterns: Mediator, Command, Chain of Responsibility, Strategy. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.

Related Patterns

More Behavioral Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.