low-level-design3 min read

Observer Design Pattern Tutorial from Scratch (2026)

Observer Design Pattern Tutorial from Scratch (2026)

Published:  |  Category: Low Level Design  |  Reading time: ~15 min
Observer Design Pattern Tutorial from Scratch (2026)

The 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. It is the foundation of publish-subscribe systems.

This pattern decouples the subject (observable) from its observers, allowing multiple views of the same data without tight coupling. It is widely used in event-driven programming, UI frameworks, and distributed systems.

Publish-Subscribe Mechanics and Event Listeners

The Observer pattern involves a Subject that maintains a list of Observers and provides methods to attach, detach, and notify them. Observers implement an update interface that the Subject calls when its state changes.

In JavaScript, this maps naturally to event emitters. The Subject emits events, and observers listen for them. This indirection allows observers to be added or removed at runtime without modifying the subject.

class Subject {
  constructor() {
    this.observers = [];
    this.state = null;
  }

  attach(observer) {
    const exists = this.observers.includes(observer);
    if (!exists) this.observers.push(observer);
  }

  detach(observer) {
    this.observers = this.observers.filter(o => o !== observer);
  }

  notify() {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }

  setState(state) {
    this.state = state;
    this.notify();
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }
  update(subject) {
    console.log(`${this.name} received state: ${subject.state}`);
  }
}

// Usage
const subject = new Subject();
const obs1 = new Observer('Observer 1');
const obs2 = new Observer('Observer 2');
subject.attach(obs1);
subject.attach(obs2);
subject.setState('Active');
subject.setState('Inactive');

Push vs Pull Models and Java EventListener Analogy

In the push model, the Subject sends detailed state data along with the notification, so observers receive all information they might need. In the pull model, the Subject sends only a minimal notification and observers query the Subject for the data they need.

The Java EventListener pattern is a classic Observer implementation. Event sources register listeners that implement a specific interface. When events occur, the source calls the appropriate method on each registered listener.

// Push model: subject sends full data
class PushSubject {
  constructor() {
    this.listeners = [];
  }
  addListener(fn) {
    this.listeners.push(fn);
  }
  emit(eventType, data) {
    for (const fn of this.listeners) {
      fn(eventType, data);
    }
  }
}

// Pull model: subject sends minimal notification
class PullSubject {
  constructor() {
    this.listeners = [];
    this.data = {};
  }
  addListener(fn) {
    this.listeners.push(fn);
  }
  notify() {
    for (const fn of this.listeners) {
      fn(this);  // observer pulls data via getters
    }
  }
  getData(key) {
    return this.data[key];
  }
  setData(key, value) {
    this.data[key] = value;
    this.notify();
  }
}

// Java EventListener style
class ButtonClickEvent {
  constructor(source, timestamp) {
    this.source = source;
    this.timestamp = timestamp;
  }
}
class Button {
  constructor(label) {
    this.label = label;
    this.clickListeners = [];
  }
  addClickListener(listener) {
    this.clickListeners.push(listener);
  }
  click() {
    const event = new ButtonClickEvent(this, Date.now());
    for (const listener of this.clickListeners) {
      listener.onClick(event);
    }
  }
}

Frequently Asked Questions

What is the difference between Observer and Publish-Subscribe patterns?

In Observer, the subject directly notifies observers via a shared interface (tight coupling). In Pub-Sub, publishers and subscribers communicate through a message broker or event channel, providing full decoupling. Pub-Sub is more scalable across distributed systems.

How do push and pull models differ in Observer?

Push sends detailed event data with the notification, making observers simpler but potentially wasteful. Pull sends minimal notification and observers fetch what they need, requiring more observer logic but reducing data transfer. The choice depends on whether observers need different subsets of data.

What are common memory issues with the Observer pattern?

The most common issue is failing to detach observers, causing memory leaks. If a subject holds strong references to observers, they cannot be garbage collected even when no longer needed. Always implement detach logic, especially in long-lived subjects like event buses.

Can Observer cause performance bottlenecks?

Yes, if notifying a large number of observers or triggering cascading updates. Notifications are synchronous by default, so a slow observer blocks all others. Solutions include async notifications, batching updates, or using a dedicated event queue.

Originally published on Ayodhyyya. Last updated June 1, 2026.