Suggest an editImprove this articleRefine the answer for “Observer pattern”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Observer pattern** - a behavioral design pattern where a subject notifies all subscribed observers automatically when its state changes. ```javascript const createSubject = () => { let observers = []; return { subscribe: (fn) => observers.push(fn), unsubscribe: (fn) => { observers = observers.filter(o => o !== fn); }, notify: (data) => observers.forEach(fn => fn(data)), }; }; ``` **Key point:** the subject never imports its observers directly - it calls their `update()` without knowing their types or count.Shown above the full answer for quick recall.Answer (EN)Image**Observer pattern** - a behavioral design pattern where a subject keeps a list of observers and notifies them automatically when its state changes. ## Theory ### TL;DR - YouTube channel analogy: subscribers get push notifications on new videos; the channel doesn't track their emails directly - Subject broadcasts changes to all listeners without knowing their types or count - Use it when 2+ objects need state sync without hard references to each other - Core mechanic: `subscribe()`, `notify()`, `unsubscribe()` - Node.js EventEmitter and React `useEffect` are both Observer implementations ### Quick example ```javascript class Subject { constructor() { this.observers = []; } subscribe(obs) { this.observers.push(obs); } unsubscribe(obs) { this.observers = this.observers.filter(o => o !== obs); } notify(data) { this.observers.forEach(obs => obs.update(data)); } } class Logger { update(data) { console.log(`Received: ${data}`); } } const subject = new Subject(); subject.subscribe(new Logger()); subject.notify('user logged in'); // Received: user logged in ``` Subject holds a plain array of observer references. `notify()` iterates and calls `update()` on each one. No direct import of `Logger` inside `Subject`. That's the point. ### Key difference Direct method calls hard-wire the caller to specific objects. If your service calls `logger.log()` directly, you can't swap `logger` without touching that service. Observer inverts this: the subject doesn't know who's listening. You add or remove observers at runtime, and the subject keeps working unchanged. This is what loose coupling means in practice. ### When to use - One object changes, many react: user logs in, you need to update the UI, refresh a cache, and send an analytics event. One `notify()` call instead of three manual ones. - Observer count varies at runtime: plugins, feature toggles, dynamic dashboards. A hardcoded list of handlers breaks the moment requirements change. - Loose sync between modules: MVC views observing a model, Redux store notifying connected components. - Skip it when a simple callback covers the case, or when you'll always have exactly one listener that never changes. ### How notify works internally In JavaScript, `notify()` iterates the observers array synchronously by default. Each `obs.update()` call goes through the prototype chain via dynamic dispatch. Node.js EventEmitter builds on this and routes async work through libuv so emits don't block the event loop. React's `useEffect` takes the same concept further: the component subscribes to a dependency array, and React schedules the effect after the render commits to the DOM. Both are the same idea at different layers of abstraction. ### Common mistakes **Forgetting unsubscribe causes memory leaks.** The observer stays in the subject's array, and the garbage collector can't reclaim it. In long-running Node servers this accumulates to hundreds of megabytes. ```javascript // Wrong: observer held in memory forever subject.subscribe(obs); // Fix: always clean up subject.subscribe(obs); const cleanup = () => subject.unsubscribe(obs); // In React: return cleanup from useEffect ``` **Synchronous notify in recursive chains causes stack overflow.** If an observer's `update()` triggers another `notify()`, you get recursion. V8 caps the call stack at around 10k frames. ```javascript // Wrong: subject.notify() -> obs.update() -> subject.notify() -> crash // Fix: break the chain with async scheduling notify(data) { setImmediate(() => this.observers.forEach(obs => obs.update(data))); } ``` **Passing mutable objects creates shared-state bugs.** Two observers get a reference to the same object. Both mutate it. State becomes unpredictable. React strict mode surfaces this quickly. ```javascript // Wrong: both observers share one reference subject.notify({ users: usersArray }); // Fix: pass a snapshot subject.notify({ users: [...usersArray] }); ``` **No error handling crashes the whole broadcast.** One observer throws, the rest never receive the notification. This has taken down production data pipelines. ```javascript notify(data) { this.observers.forEach(obs => { try { obs.update(data); } catch (err) { console.error('Observer error:', err); } }); } ``` ### Real-world usage - React: `useEffect` dependency array observes prop/state changes; cleanup function = unsubscribe - Node.js EventEmitter: `req.on('data', handler)` observes stream chunks in the core http module - Redux: `store.subscribe()` notifies connected components on dispatch; RTK Query uses the same idea for API state - RxJS: `Observable.subscribe()` is Observer plus cancellation via `Subscription` - Vue reactivity: computed properties observe reactive data and recalculate when it changes - vs Pub-Sub: Observer uses direct references (subject holds observer refs), Pub-Sub adds a broker for decoupled topic-based routing ### Follow-up questions **Q:** How do you implement Observer without classes in 10 lines? **A:** Use closures. `const createSubject = () => { let obs = []; return { subscribe: f => obs.push(f), unsubscribe: f => { obs = obs.filter(o => o !== f); }, notify: d => obs.forEach(f => f(d)) }; };`. Same contract, zero class overhead. **Q:** What is the difference between Observer and Pub-Sub? **A:** Observer is direct: the subject holds actual references to its observers. Pub-Sub adds a broker in the middle. Subscribers register for a topic, and the publisher never knows who's listening. Redis pub-sub and MQTT work this way. Choose Observer for in-process state sync, Pub-Sub for distributed systems. **Q:** How does React's `useEffect` differ from classic Observer? **A:** Classic Observer notifies immediately and synchronously. React batches changes and runs effects after the render commits to the DOM. The cleanup function maps to `unsubscribe`. The deps array defines what the component is observing. **Q:** How do you handle EventEmitter leaks in Node.js clusters? **A:** Per-fork listeners accumulate if you skip cleanup. Use `emitter.once()` for one-shot events so they auto-remove. Register `process.on('exit', cleanup)` for persistent listeners. Call `removeAllListeners()` on teardown in test environments. Juniors usually miss the `once()` pattern; seniors mention it immediately. ## Examples ### Basic Observer in TypeScript ```typescript interface IObserver { update(subject: ISubject): void; } interface ISubject { attach(observer: IObserver): void; detach(observer: IObserver): void; notify(): void; } class UserStore implements ISubject { private observers: IObserver[] = []; private loggedIn: boolean = false; attach(observer: IObserver): void { this.observers.push(observer); } detach(observer: IObserver): void { this.observers = this.observers.filter(o => o !== observer); } notify(): void { for (const observer of this.observers) { try { observer.update(this); } catch (err) { console.error('Observer error:', err); } } } login(): void { this.loggedIn = true; console.log('UserStore: user logged in'); this.notify(); } isLoggedIn(): boolean { return this.loggedIn; } } class NavbarObserver implements IObserver { update(subject: ISubject): void { if (subject instanceof UserStore && subject.isLoggedIn()) { console.log('Navbar: showing user menu'); } } } class AnalyticsObserver implements IObserver { update(subject: ISubject): void { if (subject instanceof UserStore && subject.isLoggedIn()) { console.log('Analytics: tracking login event'); } } } const store = new UserStore(); store.attach(new NavbarObserver()); store.attach(new AnalyticsObserver()); store.login(); // UserStore: user logged in // Navbar: showing user menu // Analytics: tracking login event ``` `UserStore` calls `notify()` after its state changes. Both observers react independently and know nothing about each other. Adding a third observer (a cache invalidator, for example) requires zero changes to `UserStore`. ### Node.js EventEmitter with proper cleanup Node's EventEmitter is the standard Observer implementation in the ecosystem. The memory leak trap is easy to miss on a busy server. ```javascript const { EventEmitter } = require('events'); const dataStream = new EventEmitter(); function startListening() { const handleData = (data) => { console.log('Received chunk:', data); }; dataStream.on('data', handleData); // Always return a cleanup function return () => dataStream.off('data', handleData); } const stop = startListening(); dataStream.emit('data', 'chunk 1'); // Received chunk: chunk 1 dataStream.emit('data', 'chunk 2'); // Received chunk: chunk 2 stop(); // Remove the listener dataStream.emit('data', 'chunk 3'); // Nothing printed ``` Without calling `stop()`, `handleData` stays in the listener array permanently. In a server handling thousands of requests this accumulates fast. I've seen this pattern drain 200MB of heap in a single production session. ### React useEffect as Observer React's dependency array is Observer logic built into the framework. The component subscribes to changes in specific values, and React handles the notify/unsubscribe cycle. ```jsx import { useEffect, useState } from 'react'; function PriceDisplay({ productId }) { const [price, setPrice] = useState(null); useEffect(() => { let active = true; // Guards against stale updates async function fetchPrice() { const data = await fetch(`/api/prices/${productId}`).then(r => r.json()); if (active) setPrice(data.price); } fetchPrice(); // Cleanup: unsubscribe when productId changes or component unmounts return () => { active = false; }; }, [productId]); // productId is the subject being observed return <div>Price: {price ?? 'Loading...'}</div>; } ``` When `productId` changes, React runs the cleanup (unsubscribe from the old value) then re-runs the effect (subscribe to the new value). Without the cleanup, a stale response for a previous product can update state after the component has already moved on.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.