Back to Design Patterns
Behavioral Pattern

Mediator Pattern

Define an object that encapsulates how a set of objects interact — promoting loose coupling by keeping objects from referring to each other explicitly.

Intermediate10 min read

What is the Mediator Pattern?

The Mediator pattern introduces a central object (the mediator) that all other objects communicate through, instead of communicating directly with each other. Objects become loosely coupled — they only know about the mediator, not each other.

Think of an Air Traffic Controller: planes don't talk to each other directly — they'd create chaos. They all communicate through the control tower (mediator), which coordinates landings, avoids collisions, and manages the runway queue.

This reduces the number of relationships from O(n²) (every object to every object) to O(n) (every object to the mediator).

Key Idea: Instead of many-to-many communication between objects, route all communication through one mediator. Reduces coupling from O(n²) to O(n).

Core Participants

Mediator

Mediator Interface

Declares the communication interface between colleagues. Usually a notify(sender, event) method that colleagues call to inform the mediator of state changes.

Concrete Mediator

Concrete Mediator

Implements the coordination logic. Knows all colleagues and decides how to react to events. All the complex inter-object logic lives here — not in the colleagues.

Colleague

Colleague

Any object that communicates through the mediator. Each colleague knows only about the mediator interface — never about other colleagues directly. This is the key decoupling.

!Problem It Solves

Spaghetti Object Communication

Without Mediator, every component directly references the others it needs to coordinate with. As the system grows, every new component adds references to every other component.

// Without Mediator — components reference each other directly
class CityDropdown {
    constructor(countryDropdown, stateDropdown, submitBtn) {
        this.country = countryDropdown;  // tight coupling
        this.state   = stateDropdown;    // tight coupling
        this.submit  = submitBtn;        // tight coupling
    }
    onChange() {
        this.state.updateOptions(this.country.getValue());
        this.submit.checkValid();
    }
}
// Every component is coupled to every other. A web of dependencies.

// With Mediator — components only know the mediator
class CityDropdown {
    onChange() { this.mediator.notify('city', 'changed', this.value); }
}

Mediator vs Observer

AspectMediatorObserver
CommunicationMany ↔ mediator ↔ manyOne subject → many observers
Logic lives inThe mediatorEach observer
DirectionBidirectional coordinationOne-way broadcast
AwarenessMediator knows all colleaguesSubject knows observers exist, not details

Implementation

// Mediator Pattern — Air Traffic Control

// ─── Mediator interface ───────────────────────────────────────
interface AirTrafficControl {
    void requestLanding(Aircraft aircraft);
    void notifyLanded(Aircraft aircraft);
}

// ─── Colleague ────────────────────────────────────────────────
abstract class Aircraft {
    protected AirTrafficControl atc;
    protected String flightId;

    public Aircraft(String flightId, AirTrafficControl atc) {
        this.flightId = flightId;
        this.atc      = atc;
    }
    public String getFlightId() { return flightId; }
    public abstract void land();
    public abstract void waitForClearance();
}

// ─── Concrete Colleagues ──────────────────────────────────────
class CommercialFlight extends Aircraft {
    public CommercialFlight(String id, AirTrafficControl atc) { super(id, atc); }

    public void land() {
        System.out.println("[" + flightId + "] Landing on runway...");
        atc.notifyLanded(this);
    }
    public void waitForClearance() {
        System.out.println("[" + flightId + "] Holding pattern, awaiting clearance...");
    }
    public void requestLand() { atc.requestLanding(this); }
}

// ─── Concrete Mediator ────────────────────────────────────────
class RunwayController implements AirTrafficControl {
    private boolean runwayFree = true;
    private java.util.Queue<Aircraft> queue = new java.util.LinkedList<>();

    @Override
    public void requestLanding(Aircraft aircraft) {
        if (runwayFree) {
            runwayFree = false;
            System.out.println("[ATC] Cleared " + aircraft.getFlightId() + " to land");
            aircraft.land();
        } else {
            System.out.println("[ATC] Runway busy — " + aircraft.getFlightId() + " must wait");
            aircraft.waitForClearance();
            queue.add(aircraft);
        }
    }

    @Override
    public void notifyLanded(Aircraft aircraft) {
        System.out.println("[ATC] " + aircraft.getFlightId() + " has landed. Runway clear.");
        if (!queue.isEmpty()) {
            Aircraft next = queue.poll();
            System.out.println("[ATC] Clearing " + next.getFlightId() + " from queue");
            next.land();
        } else {
            runwayFree = true;
        }
    }
}

// ─── Client ───────────────────────────────────────────────────
public class App {
    public static void main(String[] args) {
        RunwayController atc = new RunwayController();

        CommercialFlight ai101 = new CommercialFlight("AI-101", atc);
        CommercialFlight ba202 = new CommercialFlight("BA-202", atc);
        CommercialFlight ek303 = new CommercialFlight("EK-303", atc);

        ai101.requestLand(); // lands immediately
        ba202.requestLand(); // queued
        ek303.requestLand(); // queued — BA-202 lands next automatically
    }
}

When to Use Mediator

Complex UI Form Coordination

When form fields depend on each other — country changes affect city options, shipment type shows/hides fields, validation spans multiple components.

FormMediator, DialogMediator, WizardMediator

Chat / Messaging Systems

When multiple users/components need to communicate — route all messages through a chat room mediator rather than peer-to-peer.

ChatRoom, EventBus, MessageBroker

Air Traffic / Resource Scheduling

When multiple agents compete for shared resources — the mediator serialises access and manages queuing.

RunwayController, PrintSpooler, TaskScheduler

Game Event Systems

When game objects (players, enemies, environment) need to interact — route events through a game event mediator instead of direct references.

GameEventBus, CollisionMediator

Pros & Cons

Advantages

  • Reduces coupling from O(n²) to O(n)
  • Centralises complex coordination logic in one place
  • Single Responsibility — colleagues focus on their own work
  • Easy to add new colleagues — just register with mediator
  • Easy to test colleagues in isolation with a mock mediator

Disadvantages

  • Mediator can become a "God Object" knowing too much
  • All logic centralised — mediator becomes a bottleneck
  • Hard to understand the flow without reading the whole mediator
  • A bloated mediator is harder to maintain than the spaghetti it replaced

Real-World Examples

Redux Store (JavaScript)

The Redux store is a Mediator — all components dispatch actions to it, and it coordinates state updates and notifies the right subscribers. Components never talk to each other directly.

Spring Application Events (Java)

Spring's ApplicationEventPublisher lets beans publish events and other beans subscribe. The Application Context acts as the mediator — publishers and subscribers never reference each other.

RxJS / Reactive Streams

Subjects in RxJS act as mediators — multiple producers push values to a Subject; multiple consumers subscribe without knowing about the producers.

Message Brokers (Kafka, RabbitMQ)

Message brokers are infrastructure-level mediators. Producers and consumers never communicate directly — all communication is routed through topics/queues in the broker.

What's Next?

Explore related Behavioral patterns: