Observer Pattern

Med

The Observer Pattern defines a one-to-many dependency between objects so that when one object (the subject) changes state, all its dependents (observers) are notified and updated automatically. It is the backbone of event-driven programming in JavaScript.

Interactive Visualization

Creational Patterns

IIFE creates private scope, returns public API via closure
returnscloses overcallsIIFE Scopeprivate varsPublic APIConsumer
1 / 3

Understanding Observer Pattern

The Observer Pattern is the most ubiquitous design pattern in JavaScript. Every addEventListener call, every Redux subscription, every React state update, and every Node.js EventEmitter uses this pattern. Understanding it deeply is essential for both interviews and real-world development.

The pattern has two roles: the Subject (the thing being observed) and Observers (the callbacks that react to changes). The subject maintains a list of observers and calls them when its state changes. The key insight is loose coupling — the subject does not know or care what the observers do, and observers can be added or removed at any time without modifying the subject.

Memory management is the main practical concern. In single-page applications, components mount and unmount frequently. If a component subscribes to a global store or event bus but forgets to unsubscribe when it unmounts, the callback stays in memory and continues to fire. This is why modern implementations return an unsubscribe function from the subscribe call, and why React's useEffect cleanup is designed around this exact pattern.

Key Points

  • The Subject (or EventEmitter) maintains a list of observers and notifies them of state changes
  • Observers subscribe with a callback and can unsubscribe at any time
  • Promotes loose coupling — the subject does not know the observer implementations
  • Foundation of DOM events (addEventListener), Node.js EventEmitter, and reactive libraries
  • Can lead to memory leaks if observers are not properly unsubscribed
  • Synchronous by default in JS — all observers run before execution returns to the caller
  • TypeScript generics can type-safe the event names and payload shapes

Code Examples

Basic EventEmitter

class EventEmitter {
  #listeners = new Map();

  on(event, callback) {
    if (!this.#listeners.has(event)) {
      this.#listeners.set(event, []);
    }
    this.#listeners.get(event).push(callback);
    return () => this.off(event, callback); // unsubscribe fn
  }

  off(event, callback) {
    const cbs = this.#listeners.get(event);
    if (cbs) {
      this.#listeners.set(event, cbs.filter(cb => cb !== callback));
    }
  }

  emit(event, ...args) {
    const cbs = this.#listeners.get(event) || [];
    cbs.forEach(cb => cb(...args));
  }
}

const bus = new EventEmitter();
const unsub = bus.on('save', (data) => console.log('Saved:', data));
bus.emit('save', { id: 1 }); // "Saved: { id: 1 }"
unsub(); // cleanup

The on() method returns an unsubscribe function — a common pattern that prevents memory leaks.

Observable Store

function createStore(initialState) {
  let state = initialState;
  const listeners = new Set();

  return {
    getState: () => state,
    setState(updater) {
      state = typeof updater === 'function' ? updater(state) : updater;
      listeners.forEach(fn => fn(state));
    },
    subscribe(fn) {
      listeners.add(fn);
      return () => listeners.delete(fn);
    },
  };
}

const store = createStore({ count: 0 });
const unsub = store.subscribe(s => console.log('Count:', s.count));
store.setState(s => ({ count: s.count + 1 })); // "Count: 1"
unsub();

This is the core of Redux, Zustand, and similar state managers — a subject with subscribe/notify.

DOM Observer Pattern

const button = document.querySelector('#save-btn');

function logClick(e) {
  console.log('Button clicked at', e.clientX, e.clientY);
}

function trackAnalytics(e) {
  analytics.track('save_click', { x: e.clientX });
}

// Multiple observers on the same subject (button)
button.addEventListener('click', logClick);
button.addEventListener('click', trackAnalytics);

// Cleanup — remove specific observer
button.removeEventListener('click', logClick);

addEventListener is the Observer Pattern built into the browser. The button is the subject, handlers are observers.

Common Mistakes

  • Forgetting to unsubscribe — the most common cause of memory leaks in SPAs
  • Modifying the listeners array while iterating during emit, causing skipped or double-fired callbacks
  • Assuming observers fire asynchronously — in JS they are synchronous unless explicitly deferred
  • Creating circular notification loops where observer A triggers observer B which triggers A
  • Not handling errors in one observer, crashing the entire notification chain

Interview Tips

  • Implement a basic EventEmitter from scratch — this is a top-10 interview question
  • Explain how React state, Redux, and the DOM event system all use the Observer Pattern
  • Discuss the memory leak risk and how returning an unsubscribe function solves it
  • Compare Observer (subjects push to observers) with Pub/Sub (messages pass through a mediator)

Related Concepts