← Back to TypeScript Course | Chapter 16: Advanced Patterns | Lesson 4 of 7

Observer Pattern

The Observer Pattern lets one object notify multiple dependent objects when its state changes. It is useful for event systems, application state updates, and one-to-many communication.

Basic Observer

A basic observer setup has a subject that maintains a list of typed callback functions and calls each of them whenever some event or state change occurs, decoupling the thing that changes from the things reacting to it.

Example: Basic Observer

typescript
type Callback = (data: string) => void;
class Subject {
  private observers: Callback[] = [];
  subscribe(cb: Callback) { this.observers.push(cb); }
  notify(data: string) { this.observers.forEach((cb) => cb(data)); }
}
const subject = new Subject();
subject.subscribe((data) => console.log("Received:", data));
subject.notify("event fired");

Subscribe and Unsubscribe

Subscribing adds a typed callback to the subject's internal list, while unsubscribing removes it — typically by returning a cleanup function from subscribe itself so callers don't need to track the callback reference separately.

Example: Subscribe and Unsubscribe

typescript
type Callback = () => void;
class Subject {
  private observers: Callback[] = [];
  subscribe(cb: Callback): () => void {
    this.observers.push(cb);
    return () => {
      this.observers = this.observers.filter((o) => o !== cb);
    };
  }
}
const subject = new Subject();
const unsubscribe = subject.subscribe(() => console.log("notified"));
unsubscribe();
console.log("Unsubscribed");

Observers and State

Observers reacting to state changes typically receive the new state as a typed argument when notified, letting each observer update itself based on exactly what changed rather than re-fetching state independently.

Example: Observers and State

typescript
interface State { count: number; }
type Callback = (state: State) => void;
class Store {
  private observers: Callback[] = [];
  subscribe(cb: Callback) { this.observers.push(cb); }
  setState(state: State) { this.observers.forEach((cb) => cb(state)); }
}
const store = new Store();
store.subscribe((state) => console.log("New count:", state.count));
store.setState({ count: 5 });

Typed Event Objects

Typing the event object passed to observers — rather than passing loose, individual arguments — makes it easy to add new fields to what observers receive later without breaking every existing observer's function signature.

Example: Typed Event Objects

typescript
interface ClickEvent {
  x: number;
  y: number;
}
type Callback = (event: ClickEvent) => void;
class Subject {
  private observers: Callback[] = [];
  subscribe(cb: Callback) { this.observers.push(cb); }
  notify(event: ClickEvent) { this.observers.forEach((cb) => cb(event)); }
}
const subject = new Subject();
subject.subscribe((e) => console.log(e.x, e.y));
subject.notify({ x: 10, y: 20 });

When to Use Observer

Reach for the observer pattern when multiple independent parts of a program need to react to the same event source, like UI components all needing to update when shared data changes — for a single consumer, a plain callback is simpler.

Example: When to Use Observer

typescript
type Callback = (value: number) => void;
class DataSource {
  private observers: Callback[] = [];
  subscribe(cb: Callback) { this.observers.push(cb); }
  update(value: number) { this.observers.forEach((cb) => cb(value)); }
}
const source = new DataSource();
source.subscribe((v) => console.log("Widget A:", v));
source.subscribe((v) => console.log("Widget B:", v));
source.update(42);
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.