Mediator / Pub-Sub Pattern

Hard

The Mediator Pattern centralizes communication between objects through a single coordinator, preventing direct object-to-object coupling. Pub/Sub is a specialized form where publishers emit named events and subscribers listen for them through a shared bus, enabling fully decoupled architectures.

Interactive Visualization

Creational Patterns

IIFE creates private scope, returns public API via closure
returnscloses overcallsIIFE Scopeprivate varsPublic APIConsumer
1 / 3

Understanding Mediator / Pub-Sub Pattern

The Mediator Pattern replaces direct object-to-object communication with centralized coordination. Instead of component A calling component B directly, both communicate through a mediator. This eliminates the web of dependencies that forms when N components each reference M others, replacing it with a star topology where every component only knows the mediator.

Pub/Sub is the most common specialization of the Mediator Pattern in JavaScript. Publishers emit named events with data, and subscribers register callbacks for event names they care about. The event bus is the mediator. This is the architecture behind Redux (dispatch is publish, subscribe is subscribe), Node.js EventEmitter in large systems, and browser CustomEvents.

The main risk is that the mediator can become a god object — a single class that knows too much and does too much. The mediator should only route messages, not contain business logic. Another concern is debuggability: when communication flows through events, it is harder to trace the path from cause to effect compared to direct function calls. For this reason, typed event buses (with TypeScript generics constraining event names and payloads) are preferred in production codebases.

Key Points

  • The mediator encapsulates how a set of objects interact, promoting loose coupling
  • Objects communicate through the mediator instead of referencing each other directly
  • Pub/Sub extends this with named channels — publishers and subscribers never know about each other
  • Widely used in chat systems, form validation, UI component coordination, and microservice messaging
  • Can become a god object if too much logic is placed in the mediator itself
  • TypeScript generics can enforce type-safe event maps for the event bus
  • Different from Observer: Observer is one-to-many from a subject, Mediator is many-to-many through a hub

Code Examples

Type-Safe Event Bus (Pub/Sub)

class EventBus {
  #channels = new Map();

  publish(channel, data) {
    const subs = this.#channels.get(channel) || [];
    subs.forEach(fn => fn(data));
  }

  subscribe(channel, callback) {
    if (!this.#channels.has(channel)) {
      this.#channels.set(channel, []);
    }
    this.#channels.get(channel).push(callback);
    return () => {
      const subs = this.#channels.get(channel);
      this.#channels.set(channel, subs.filter(fn => fn !== callback));
    };
  }
}

const bus = new EventBus();

// Component A — publisher
bus.publish('cart:updated', { items: 3, total: 99.99 });

// Component B — subscriber (no reference to A)
const unsub = bus.subscribe('cart:updated', (data) => {
  console.log('Cart badge:', data.items);
});

Component A and B communicate through named channels. Neither knows the other exists.

Chat Room Mediator

class ChatRoom {
  #users = new Map();

  register(user) {
    this.#users.set(user.name, user);
    user.chatRoom = this;
  }

  send(message, from, to) {
    if (to) {
      // Direct message
      const recipient = this.#users.get(to);
      if (recipient) recipient.receive(message, from);
    } else {
      // Broadcast
      this.#users.forEach((user, name) => {
        if (name !== from) user.receive(message, from);
      });
    }
  }
}

class User {
  constructor(name) { this.name = name; }
  send(message, to) { this.chatRoom.send(message, this.name, to); }
  receive(message, from) { console.log(this.name + ' received from ' + from + ': ' + message); }
}

const room = new ChatRoom();
const alice = new User('Alice');
const bob = new User('Bob');
room.register(alice);
room.register(bob);
alice.send('Hello!'); // Bob receives

Users communicate through the ChatRoom mediator. Adding or removing users requires no changes to other users.

Form Validation Mediator

class FormMediator {
  #fields = new Map();
  #submitBtn = null;

  registerField(name, element, validator) {
    this.#fields.set(name, { element, validator, valid: false });
    element.addEventListener('input', () => this.validate(name));
  }

  registerSubmit(btn) {
    this.#submitBtn = btn;
    this.updateSubmitState();
  }

  validate(name) {
    const field = this.#fields.get(name);
    field.valid = field.validator(field.element.value);
    field.element.style.borderColor = field.valid ? 'green' : 'red';
    this.updateSubmitState();
  }

  updateSubmitState() {
    const allValid = [...this.#fields.values()].every(f => f.valid);
    if (this.#submitBtn) this.#submitBtn.disabled = !allValid;
  }
}

const form = new FormMediator();
form.registerField('email', emailInput, v => v.includes('@'));
form.registerField('name', nameInput, v => v.length > 0);
form.registerSubmit(submitBtn);

Fields do not know about each other or the submit button. The mediator coordinates validation and submit state.

Common Mistakes

  • Putting too much business logic in the mediator, turning it into a god object
  • Not namespacing events in large Pub/Sub systems, causing event name collisions
  • Forgetting to unsubscribe, creating memory leaks identical to the Observer Pattern
  • Using Pub/Sub when direct function calls would be simpler and more debuggable
  • Creating circular publish chains that cause infinite loops

Interview Tips

  • Clearly distinguish Mediator from Observer — Mediator is many-to-many through a hub, Observer is one-to-many from a subject
  • Mention real-world examples: Redux (store as mediator), message queues, chat systems
  • Discuss the trade-off: loose coupling vs harder debugging (events are harder to trace than direct calls)
  • Know when NOT to use it — small apps with 2-3 components communicating directly are simpler without a mediator

Related Concepts