DEV Community

Sachin Kasana
Sachin Kasana

Posted on • Originally published at sachinkasana.Medium on

️‍️ Design Patterns in JavaScript — Part 3: The Observer Pattern

Design Patterns in JavaScript -Part 3: The Observer Pattern

“React to changes. Decouple logic. Keep your code alive.”

Imagine this:

  • A button is clicked… and five other components need to react.
  • Or a cart is updated, and multiple parts of the UI need to re-render.
  • Or a websocket receives new data, and you want to broadcast it cleanly.

This is exactly what the Observer Pattern is made for.

Let’s break it down — clearly, practically, and with real JavaScript examples.

🧠 What is the Observer Pattern?

It’s a behavioral design pattern where:

  • One object (subject) maintains a list of dependents (observers)
  • Whenever something changes, it notifies all observers

Sound familiar? That’s how EventEmitter, Redux, and even useEffect() work under the hood.

📦 Use Case 1: Custom Event Emitter in Vanilla JS

Let’s build a simple custom emitter:

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, fn) {
    (this.events[event] ||= []).push(fn);
  }

  emit(event, data) {
    (this.events[event] || []).forEach(fn => fn(data));
  }

  off(event, fnToRemove) {
    this.events[event] = (this.events[event] || []).filter(fn => fn !== fnToRemove);
  }
}
Enter fullscreen mode Exit fullscreen mode

Usage:

const emitter = new EventEmitter();

function onUserLogin(data) {
  console.log('User logged in:', data);
}

emitter.on('login', onUserLogin);
emitter.emit('login', { name: 'Sachin' }); // logs: User logged in: { name: 'Sachin' }
Enter fullscreen mode Exit fullscreen mode

✅ Decoupled logic

✅ Reusable in any JS app

✅ Mimics Node.js EventEmitter or window.addEventListener

⚛️ Use Case 2: React State Subscriptions

You can implement a mini Redux-style observer pattern:

const listeners = [];

export const store = {
  state: { count: 0 },

  subscribe(fn) {
    listeners.push(fn);
  },

  setState(newState) {
    this.state = { ...this.state, ...newState };
    listeners.forEach(fn => fn(this.state));
  },
};
Enter fullscreen mode Exit fullscreen mode

React component:

useEffect(() => {
  store.subscribe(setLocalState);
}, []);
Enter fullscreen mode Exit fullscreen mode

🧠 Now any part of your app can react to global changes without tight coupling.

📡 Use Case 3: Broadcasting WebSocket Data

Imagine a stock ticker app:

ws.onmessage = (msg) => {
  observers.forEach((fn) => fn(msg.data));
};

function subscribeToStock(fn) {
  observers.push(fn);
}
Enter fullscreen mode Exit fullscreen mode

Now your price chart, recent trades table, and alert component can independently subscribe.

🧪 Real Project Examples

Design pattern usage

🧠 TL;DR

when we should use observable design pattern

🔜 Coming Up Next: Strategy Pattern

In Part 4, we’ll look at how to create pluggable logic using the Strategy Pattern — useful for:

  • Payment flow
  • Sorting & filtering
  • Validation systems
  • Rule engines

👉 Follow on Medium to get the next drop — clean code, real use cases, no fluff.

Top comments (0)