# Observer, Pub/Sub and Event Emitters — Design Patterns

Source: https://www.skillbyai.com/en/design-patterns/b-observer

> Notify many listeners without knowing them.

## Subjects and subscribers

**Intent:** define a one-to-many dependency so that when a subject changes, all its observers are notified automatically. The subject keeps a list of listeners and calls them; it does not know what they do. This decouples, for example, an `OrderService` from the email, analytics and loyalty modules that react to "order placed". Variants: Node.js `EventEmitter` and the DOM `EventTarget` (in-process events), **pub/sub** with a broker in between (Redis, Kafka, cloud queues), and reactive streams (RxJS observables). **Watch out** for memory leaks from listeners that are never removed, hard-to-trace control flow ("who reacts to this event?"), and error handling: decide whether one failing listener should affect others.

## A typed event bus

Listeners subscribe and receive an unsubscribe function.

```typescript
type Events = {
  'order.placed': { orderId: string; customerEmail: string; totalCents: number };
  'order.cancelled': { orderId: string; reason: string };
};

class EventBus<E extends Record<string, unknown>> {
  private listeners: { [K in keyof E]?: Array<(payload: E[K]) => void> } = {};

  on<K extends keyof E>(event: K, fn: (payload: E[K]) => void): () => void {
    (this.listeners[event] ??= []).push(fn);
    return () => { this.listeners[event] = this.listeners[event]!.filter((f) => f !== fn); };
  }

  emit<K extends keyof E>(event: K, payload: E[K]) {
    for (const fn of this.listeners[event] ?? []) {
      try { fn(payload); } catch (err) { logger.error({ err, event }, 'listener failed'); }
    }
  }
}

const bus = new EventBus<Events>();
bus.on('order.placed', (e) => mailer.sendReceipt(e.customerEmail, e.orderId));
bus.on('order.placed', (e) => analytics.track('purchase', { value: e.totalCents }));
const unsubscribe = bus.on('order.cancelled', (e) => inventory.restock(e.orderId));

bus.emit('order.placed', { orderId: 'o-1', customerEmail: 'asha@example.com', totalCents: 2599 });
```

## Always keep the unsubscribe

In UI code, subscribe in a mount or effect hook and unsubscribe in its cleanup; forgetting this is a classic source of leaks and duplicate handlers.

**Quiz:** What is a common pitfall of the Observer pattern?

- [ ] It cannot have more than one observer
- [ ] It requires a database
- [x] Listeners that are never removed, causing memory leaks or duplicate handling
- [ ] It forces synchronous network calls

*Answer:* Listeners that are never removed, causing memory leaks or duplicate handling. Subscriptions need a lifecycle.
