Skip to content

Observer Pattern β€” Pub/Sub, Event Listeners (2026)

DodaTech Updated 2026-06-20 5 min read

In this tutorial, you'll learn how the observer pattern enables one-to-many communication between objects, how publish-subscribe systems decouple event producers from consumers, and how modern reactive frameworks use this pattern under the hood.

A YouTube channel is the perfect analogy. You subscribe to a creator. When they upload a new video, every subscriber gets notified automatically. The creator doesn't personally message each subscriber β€” they just publish to their channel. The platform handles distribution. If you unsubscribe, you stop getting notifications. The creator never needs to know who subscribed or unsubscribed. That's the observer pattern: a subject (channel) notifies observers (subscribers) without knowing anything about them.

Core Concept

The observer pattern defines a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified automatically. This is also known as publish-subscribe (pub/sub) or the event-listener pattern.

// The observer interface β€” anything that wants notifications
interface Observer {
  update(event: string, data: unknown): void;
}

// The subject β€” manages observers and sends notifications
class EventBus {
  private observers: Map<string, Observer[]> = new Map();

  subscribe(event: string, observer: Observer): void {
    if (!this.observers.has(event)) {
      this.observers.set(event, []);
    }
    this.observers.get(event)!.push(observer);
  }

  unsubscribe(event: string, observer: Observer): void {
    const list = this.observers.get(event);
    if (!list) return;
    const index = list.indexOf(observer);
    if (index !== -1) list.splice(index, 1);
  }

  emit(event: string, data: unknown): void {
    const list = this.observers.get(event);
    if (!list) return;
    list.forEach(observer => observer.update(event, data));
  }
}

How It Works

The subject maintains a list of observers. Observers register themselves for specific events. When the subject's state changes, it iterates over the relevant observers and calls their update method. The subject doesn't know what observers do β€” it just notifies them.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     notify      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚             β”‚ ──────────────→ β”‚  Observer A  β”‚
β”‚   Subject   β”‚                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  (EventBus) β”‚     notify      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚             β”‚ ──────────────→ β”‚  Observer B  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ subscribe / unsubscribe
       ↓
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Observersβ”‚  (any number)
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Browser Event Listener Example

The browser's DOM event system is a textbook observer pattern:

// The subject is the DOM element
const button = document.getElementById("submitBtn");

// Observers register via addEventListener
button.addEventListener("click", () => {
  console.log("Button was clicked!");
});

button.addEventListener("click", () => {
  console.log("Also logging the click");
});

// When the button is clicked, BOTH observers fire
// Output:
// Button was clicked!
// Also logging the click

Custom Event System

Here's a complete pub/sub implementation for an application event system:

class AppEventBus {
  private handlers: Map<string, Set<Function>> = new Map();

  on(event: string, handler: Function): void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler);
  }

  off(event: string, handler: Function): void {
    this.handlers.get(event)?.delete(handler);
  }

  emit(event: string, ...args: unknown[]): void {
    this.handlers.get(event)?.forEach(handler => {
      handler(...args);
    });
  }
}

// Usage
const bus = new AppEventBus();

bus.on("user:login", (user) => {
  console.log(`Welcome back, ${user.name}`);
});

bus.on("user:login", (user) => {
  // Log security event
  auditLogger.log(`User ${user.id} logged in`);
});

bus.emit("user:login", { id: 1, name: "Alice" });
// Output:
// Welcome back, Alice
// User 1 logged in

Expected output: The event bus notifies all registered handlers for the user:login event. Adding or removing handlers at runtime doesn't affect other handlers.

Real-World Examples

Reactive Programming (RxJS)

RxJS is built entirely on the observer pattern. Observables are subjects, and observers subscribe to receive values over time. Angular's HTTP client returns Observables β€” when data arrives, all subscribers are notified.

import { Observable } from "rxjs";

const dataStream = new Observable(subscriber => {
  subscriber.next("First value");
  subscriber.next("Second value");
  subscriber.complete();
});

dataStream.subscribe(value => console.log(value));
// Output:
// First value
// Second value

Notification Systems

Doda Browser's extension system uses an internal event bus. When an extension triggers an action (download complete, tab closed, bookmark added), the event bus notifies all registered listeners. Extensions can subscribe to events without knowing about each other.

Pros & Cons

Pros Cons
Loose coupling between subject and observers Memory leaks if observers aren't unregistered
Supports broadcast communication Debugging is harder (unclear who receives events)
Observers can be added/removed at runtime Order of notification isn't guaranteed
Natural fit for event-driven systems Can cause cascading updates if not designed carefully

When to Use

Use the observer pattern when:

  • One object needs to notify many others without knowing who they are
  • Changes to one object require updating related objects
  • You're building event-driven or reactive systems
  • You want loose coupling between components that need to communicate

Avoid it when the notification chain is simple enough for direct calls, or when every observer needs to be notified in a specific order.

FAQ

What's the difference between Observer and Pub/Sub?

In the observer pattern, observers know about the subject. In pub/sub, publishers and subscribers are fully decoupled by a message broker β€” they never reference each other. Pub/sub is the observer pattern's more decoupled cousin.

How do I prevent memory leaks with observers?

Always provide an unsubscribe mechanism and call it when the observer is no longer needed. In component-based frameworks (React, Angular, Vue), clean up in the lifecycle hook (useEffect cleanup, ngOnDestroy, onUnmounted).

How does this compare to the Strategy Pattern?

The Strategy Pattern lets you swap algorithms at runtime. The observer pattern lets you notify multiple objects of state changes. Strategy is about interchangeable behavior; observer is about event notification. They solve different problems and can be used together.

What is a 'subject' in reactive programming terms?

A subject is both an observable and an observer. It can subscribe to other observables and pass through values, or you can manually push values into it. Subjects are the core building block of multicasting in RxJS.

Can the observer pattern cause performance issues?

Yes β€” with many observers or frequent events, notification overhead adds up. Slow observers block fast ones if notifications are synchronous. Solutions: use asynchronous notification (queues, microtasks), throttle frequent events, or use a dedicated event system with backpressure support

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro