BuildBot

Patterns That Matter

Observer & reactivity

Lesson 2 of 5

What you'll learn

  • Recognize the Observer pattern as pub/sub, the foundation of reactive systems.
  • See how signals and event emitters are Observer with better ergonomics.
  • Build a tiny emitter with subscribe and emit that notifies all subscribers.

Observer is pub/sub

The Observer pattern is a subject that keeps a list of observers and notifies them when something changes. Strip the class hierarchy and it is pub/sub: a set of callbacks, plus a way to add and trigger them. Every event emitter you have used is this pattern.

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

function emitter<T>() {
  const listeners = new Set<Listener<T>>();
  return {
    subscribe(fn: Listener<T>) {
      listeners.add(fn);
      return () => listeners.delete(fn); // unsubscribe
    },
    emit(value: T) {
      for (const fn of listeners) fn(value);
    },
  };
}

The returned unsubscribe function matters. The classic pattern often forgets teardown, which is how you leak listeners. Returning the disposer at subscribe time is the modern convention — React effects, RxJS, and signal libraries all do it.

Signals are Observer with dependency tracking

Frameworks like Solid, Angular signals, and Preact signals add one thing on top of pub/sub: automatic dependency tracking. When you read a signal inside a computation, the computation subscribes to it for you.

// A signal: a value plus an emitter, with reads tracked automatically.
function signal<T>(initial: T) {
  let value = initial;
  const bus = emitter<T>();
  return {
    get: () => value,
    set: (next: T) => {
      value = next;
      bus.emit(value); // notify everyone who depends on it
    },
    subscribe: bus.subscribe,
  };
}

That is the entire conceptual leap from Observer to fine-grained reactivity. The hard engineering is in the tracking and scheduling, not in the pattern itself.

Observer vs. event bus

A global event bus is Observer scaled up, and it is easy to abuse. When everything talks through one bus, data flow becomes invisible. Prefer scoped emitters and explicit subscriptions you can trace.

A tiny signal

Run the emitter. Subscribe two listeners, emit a couple of values, then unsubscribe one and emit again. Watch how only live subscribers are notified.

Loading editor…
Knowledge check

What does a signal add on top of plain pub/sub Observer?

Saved on this device. Sign in to sync your progress everywhere.