cv.cm

Coding · Design patterns

Observer pattern explained with examples

5 min read

Short answer

Observer is a behavioral design pattern in which an object, called the subject, keeps a list of dependents, called observers, and notifies them automatically when its state changes. Observers subscribe and unsubscribe at runtime, and the subject does not need to know their concrete classes. Event listeners in the browser, Node.js EventEmitter and reactive libraries such as RxJS are all built on this idea.

Intent and the problem it solves

Intent: define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Observer is one of the behavioral patterns in the Gang of Four (GoF) book, and it is also known as publish-subscribe or the listener pattern.

Imagine a shopping cart in a web shop. When an item is added, the cart icon in the header must show the new count, the sidebar must show the new total, and an analytics module must record the event. If the cart calls each of these directly, the cart module depends on the header, the sidebar and analytics. Adding a fourth reaction means editing the cart again.

With Observer, the cart only keeps a list of subscribers and calls each of them when it changes. The header, sidebar and analytics subscribe themselves. The cart does not import them, and new features can react to cart changes without touching the cart code.

Structure and participants

RoleResponsibility
Subject (publisher)Keeps a list of observers and provides methods to subscribe and unsubscribe them; notifies them when something changes
Observer (subscriber)Interface with an update method, often just a callback function
Concrete subjectHolds the state of interest and calls notify when it changes
Concrete observerReacts to notifications, for example by redrawing part of the UI

There are two styles of notification. In the push model the subject sends the changed data along with the notification. In the pull model it only says "something changed" and observers ask the subject for the data they need.

TypeScript implementation

A small generic Subject class handles subscriptions. Observers are plain functions, which is the most common form in TypeScript. subscribe returns a function that unsubscribes, so callers cannot forget which listener they registered.

type Listener<T> = (value: T) => void;

class Subject<T> {
  private listeners = new Set<Listener<T>>();

  subscribe(listener: Listener<T>): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  protected notify(value: T) {
    for (const listener of [...this.listeners]) listener(value);
  }
}

type CartEvent = { count: number; total: number };

class Cart extends Subject<CartEvent> {
  private items: { name: string; price: number }[] = [];

  add(name: string, price: number) {
    this.items.push({ name, price });
    const total = this.items.reduce((sum, item) => sum + item.price, 0);
    this.notify({ count: this.items.length, total });
  }
}

const cart = new Cart();
cart.subscribe(({ count }) => console.log("header: " + count + " items"));
const stopAnalytics = cart.subscribe(({ total }) => console.log("analytics: total " + total));

cart.add("Book", 12);
stopAnalytics(); // unsubscribe
cart.add("Pen", 3);
// header: 1 items
// analytics: total 12
// header: 2 items

notify iterates over a copy of the set ([...this.listeners]). This way a listener that unsubscribes itself during notification does not disturb the loop.

Using the built-in EventTarget

Browsers, Node.js and Deno provide EventTarget, so you often do not need your own subject class:

const thermostat = new EventTarget();

thermostat.addEventListener("change", (e) => {
  console.log("new temperature:", (e as CustomEvent<number>).detail);
});

thermostat.dispatchEvent(new CustomEvent("change", { detail: 21.5 }));
// new temperature: 21.5

Node.js also has the EventEmitter class in the node:events module, with on, off and emit methods.

When to use it and when not to

  • Use it when a change in one object must update other objects, and you do not know in advance how many or which ones.
  • Use it to keep a core module independent of UI, logging or analytics code that reacts to it.
  • Use it for events that happen over time, such as user input, messages or sensor readings.
  • Do not use it when there is exactly one fixed receiver; a direct method call or callback parameter is easier to follow.
  • Be careful with chains of observers that trigger other notifications. They can cause update loops and make it hard to see why something changed.
  • Do not rely on the order in which observers are called unless the subject documents it.

Real-world uses

  • The DOM: element.addEventListener("click", handler) subscribes an observer to an element.
  • Node.js: EventEmitter is the base of streams, HTTP servers and many other core objects.
  • Browser observer APIs such as MutationObserver, IntersectionObserver and ResizeObserver notify callbacks about changes to the page.
  • RxJS models streams of values as observables that observers subscribe to.
  • Java: java.util.Observable was deprecated in Java 9; the java.util.concurrent.Flow interfaces (publisher and subscriber) and event listener interfaces are used instead.

Related patterns

PatternHow it differs from Observer
MediatorA mediator knows the participating objects and coordinates them; a subject simply broadcasts and does not know what observers do.
Publish-subscribe with a brokerPublishers and subscribers do not know each other at all and communicate through a message broker or event bus, often between processes.
CommandA notification can carry a command object; Command is about one request, Observer about one-to-many notification.
IteratorIterator is pull-based (the consumer asks for the next value); Observer is push-based (the producer sends values when they occur).

FAQ

What is the difference between Observer and publish-subscribe?

In the classic Observer pattern, observers register directly with the subject. In publish-subscribe, a broker or event channel sits between them, so publishers and subscribers do not know about each other. Many people use the two names loosely for the same idea.

How do I avoid memory leaks with the Observer pattern?

Unsubscribe observers when they are no longer needed. Returning an unsubscribe function from subscribe, or using an AbortSignal with addEventListener, makes cleanup simple.

Are observers called synchronously?

In the example above and in EventTarget and EventEmitter, listeners run synchronously in the order they were added. Other libraries may deliver notifications asynchronously, so check their documentation.

Is Observer the same as reactive programming?

Reactive programming builds on Observer. Libraries such as RxJS add operators for filtering, combining and transforming streams of notifications, plus rules for errors and completion.

All coding tutorials