DEV Community

Cover image for Observer Pattern in Modern JavaScript
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Observer Pattern in Modern JavaScript

A distilled guide based on “Learning Patterns” by Lydia Hallie & Addy Osmani

The Observer Pattern establishes a one‑to‑many relationship between an object that owns some state (the observable) and the dependents that need to react when that state changes (the observers). Whenever an event occurs, the observable notifies every subscribed observer.


1. Anatomy of the Pattern

An observable usually exposes three core capabilities:

Part Responsibility
observers Holds a list of callbacks (observers)
subscribe(fn) Registers an observer
unsubscribe(fn) Removes an observer
notify(data) Invokes every observer with the new data

Minimal ES 6 Implementation

class Observable {
  constructor() {
    this.observers = [];
  }
  subscribe(fn) {
    this.observers.push(fn);
  }
  unsubscribe(fn) {
    this.observers = this.observers.filter(obs => obs !== fn);
  }
  notify(data) {
    this.observers.forEach(obs => obs(data));
  }
}
Enter fullscreen mode Exit fullscreen mode

This is exactly the skeleton Lydia & Addy showcase before applying it to a real app.


2. From Theory to Practice — A Tiny App

Below is a condensed version of their example: a button and a switch that both report user activity. Whenever a user clicks or toggles, we log to the console and fire a toast notification—two observers reacting to one observable.

import { ToastContainer, toast } from "react-toastify";
import { Button, Switch, FormControlLabel } from "@material-ui/core";

const analytics$ = new Observable();          // ① create observable

function logger(data) {                       // ② observer 1
  console.log(Date.now(), data);
}

function toastify(data) {                     // ② observer 2
  toast(data);
}

analytics$.subscribe(logger);                 // ③ subscribe
analytics$.subscribe(toastify);

export default function App() {
  const handleClick  = () => analytics$.notify("Clicked the button!");
  const handleToggle = () => analytics$.notify("Toggled the switch!");

  return (
    <div className="App">
      <Button onClick={handleClick}>Click me!</Button>
      <FormControlLabel control={<Switch onChange={handleToggle} />} />
      <ToastContainer />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Result: one source of truth (analytics$) with multiple independent reactions. This separation keeps concerns tidy and testable.


3. Five Real‑Life Software Use‑Cases

# Use‑Case Where the Pattern Shows Up Benefit
1 DOM events Every addEventListener call makes your callback an observer of a DOM element. Decouples UI elements from event logic.
2 Node.js EventEmitter Streams, HTTP servers, and many core modules emit events you can subscribe to. Enables modular, event‑driven servers.
3 Redux .subscribe() Components observe the store; they rerender only when relevant state changes. Keeps UI synced with global state without tight coupling.
4 RxJS Observables Reactive extensions combine Observer and Iterator patterns to manage async data. Composable pipelines for events, promises, WebSockets…
5 IntersectionObserver API Lazy‑load images by observing when elements enter the viewport. Boosts performance with minimal code.
6 WebSocket chat apps Clients subscribe to a channel; server notifies on new messages. Real‑time updates without polling.

Tip: Anytime you have “Something happened, tell everyone who cares,” the Observer Pattern is a candidate.


4. Pros & Cons

✅ Pros ❌ Cons
Enforces separation of concerns—observables don’t know what observers do with the data. Lots of observers can mean performance overhead if notifications are frequent or expensive.
Promotes loose coupling; observers can attach/detach at runtime. Debugging can be trickier: side‑effects live in many places.

5. Key Takeaways

  1. Keep the observable small & focused—just manage subscriptions and dispatch events.
  2. Extract complex reaction logic into observers for clear responsibility boundaries.
  3. Combine with other patterns (e.g., Provider or Middleware) for scalable architectures.

Content adapted under CC BY‑NC 4.0 from patterns.dev.

Top comments (0)