Home › Low-Level Design › Mediator Pattern
M

Mediator Pattern

Stop objects from calling each other directly. Put a coordinator in the middle, and let it do the talking.

Low-Level DesignMedium9 min read

I reach for this pattern the moment I see a mesh of objects all holding references to each other. It is the untangling move, and in a loop at Google, Amazon, or any top shop, interviewers watch closely for whether you make that call on your own. This is an LLD design pattern, so I will teach it the way you would actually use it with a whiteboard marker in hand.

If you remember five things

The whole pattern in one glance. Everything below defends these five calls.

  1. Intent. Replace a mesh of objects that all reference each other with a single hub they all talk to instead.
  2. Star, not web. Colleagues point at one mediator, the mediator points back out. Edge count drops from n squared to n.
  3. Rules live in one place. The "who talks to whom and under what conditions" logic moves out of the colleagues and into the mediator's one method.
  4. Colleagues go dumb. A colleague reports an event up and reacts to calls coming down. It never names a peer.
  5. The gotcha. All that logic has to land somewhere, so the mediator can swell into a god object. That tradeoff is the thing you name before the interviewer does.

1The problem it solves#

Start with the mess the pattern is meant to kill: too many objects knowing too much about each other.

Picture a chat room with five users. Each user needs to send messages to every other user.

  • If each User holds a direct reference to the other four, you get a dependency graph shaped like spaghetti.
  • Add a sixth user and you wire up five more references.
  • This is the classic many-to-many mess, and it grows like n squared: 5 users is 20 directed edges, 10 users is 90, 50 users is 2,450.
  • The wiring cost explodes long before the feature list does.

Now add a rule and watch it get worse. Say a message should be logged, filtered for profanity, and skipped if the recipient has blocked the sender.

  • If each User talks to each other User directly, that policy gets copy-pasted into every object, or smeared across all of them.
  • Change one rule and you are hunting through every class to patch the same three lines.

I have seen this exact shape in real code. UI forms where a checkbox enables a text field, which changes a dropdown, which disables a button.

  • Every widget knows about three other widgets.
  • Touch one and something unrelated breaks two screens away.

The root cause is coupling, and the Mediator answer is blunt: the objects should not talk to each other at all.

  • They talk to one object in the middle, and that object decides what happens next.
  • This is the Single Responsibility Principle applied to interaction: "who talks to whom, under what rules" becomes one object's job.
  • It is also Dependency Inversion: colleagues point at a Mediator interface, never at concrete peers.
The one-line test for this pattern

If you can count the direct references between peers and the number scares you, that is the signal. The Mediator trades n-squared peer edges for n edges to a single hub. You are not removing the complexity, you are relocating it to one readable, testable place.

2The idea (mental model)#

The cleanest picture here is an air traffic control tower.

  • Planes do not radio each other to coordinate landings. That would be chaos.
  • Every plane talks only to the tower, and the tower tells each plane what to do.
  • The planes do not even know the other planes exist. They just know the tower.

That is the whole pattern.

  • The planes are your Colleague objects. The tower is the Mediator.
  • Instead of a web of connections, you get a star: everything points to the center, and the center points back out.
  • The edge count drops from n squared to n.

The key shift in thinking: a colleague gives up control of the interaction.

  • A colleague no longer says "send this message to Bob."
  • It says "here is a message, Mediator, you figure out where it goes."
  • The colleague owns the what. The mediator owns the how.
  • Result: n-to-n relationships become n-to-1, and you get a single place to put interaction rules.

3Structure#

Four roles, and the asymmetry between them is the point.

  • Mediator (interface). Declares how colleagues notify it, usually something like notify(sender, event).
  • ConcreteMediator. Holds references to all the colleagues and contains the actual coordination logic. This is where the brains live.
  • Colleague (abstract or interface). Each colleague holds a reference to the mediator and nothing else about its peers.
  • ConcreteColleague. The real objects (users, widgets, planes). They report events up to the mediator and react to calls coming down.

The asymmetry in one line: the colleagues are dumb about each other, the mediator is smart about all of them.

classDiagram class Mediator { <> +send(message String sender User) void } class ChatRoom { -List~User~ users +register(user User) void +send(message String sender User) void } class User { <> #Mediator mediator #String name +send(message String) void +receive(message String from String) void } class ChatUser { +receive(message String from String) void } Mediator <|.. ChatRoom User <|-- ChatUser ChatRoom o-- User User --> Mediator
Notice two things. ChatRoom o-- User is aggregation: the room holds users but does not own their lifecycle. User --> Mediator means every user points only at the mediator. No arrow runs from one user to another, and that empty center is the win.

4Implementation in Java#

Here is a chat room. Minimal, but it shows every role.

interface Mediator {
    void send(String message, User sender);
    void register(User user);
}

abstract class User {
    protected Mediator mediator;
    protected String name;

    User(Mediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }

    public void send(String message) {
        mediator.send(message, this);
    }

    public abstract void receive(String message, String from);
}

class ChatUser extends User {
    ChatUser(Mediator mediator, String name) {
        super(mediator, name);
    }

    @Override
    public void receive(String message, String from) {
        System.out.println(name + " got from " + from + ": " + message);
    }
}

class ChatRoom implements Mediator {
    private final List<User> users = new ArrayList<>();

    @Override
    public void register(User user) {
        users.add(user);
    }

    @Override
    public void send(String message, User sender) {
        // All the interaction rules live HERE, in one place.
        for (User u : users) {
            if (u != sender) {
                u.receive(message, sender.name);
            }
        }
    }
}

Wiring it up:

Mediator room = new ChatRoom();
User alice = new ChatUser(room, "Alice");
User bob = new ChatUser(room, "Bob");
User carol = new ChatUser(room, "Carol");

room.register(alice);
room.register(bob);
room.register(carol);

alice.send("hey everyone");
// Bob and Carol receive it. Alice never touched them directly.

Now watch how cheap it is to add a rule. Want to block profanity, skip muted users, or log every message?

  • You change ChatRoom.send. One method.
  • The User classes do not move at all.
  • That is the payoff for all the indirection.
@Override
public void send(String message, User sender) {
    String clean = filterProfanity(message);
    audit.log(sender.name, clean);
    for (User u : users) {
        if (u != sender && !isMuted(sender, u)) {
            u.receive(clean, sender.name);
        }
    }
}
Pause before you read on: a new teammate adds a "block list" feature by giving each `User` a `Set<User> blocked` and checking it inside `receive`. Did they implement the pattern or break it?
Reveal the answer
They broke it. A `User` now holds references to other `User` objects, which is exactly the peer coupling the mediator was supposed to remove. The block check belongs in `ChatRoom.send`, where the mediator already knows both sender and recipient. The rule is simple: if a colleague ever names or points at a peer, you have quietly rebuilt the mesh.

5A real-world example#

The one I hit most often is a complex form or dashboard.

Think of a flight booking screen with a "round trip" toggle, a return-date picker, a passenger count, and a "search" button. The rules are tangled:

  • Checking "one way" should gray out the return date.
  • Zero passengers should disable search.
  • Picking a date in the past should show a warning.

Without a mediator, the widgets reach into each other.

  • The checkbox listener reaches into the date picker, the date picker reaches into the button.
  • Six months later nobody can tell what enables what.

A FormMediator fixes this.

  • Each widget reports "I changed" to the mediator.
  • The mediator holds all the enable/disable logic in one onChange(widget) method.
  • Widgets stay dumb. The rules stay in one readable place.

Same shape shows up at bigger scale.

  • Message brokers and event buses: services publish to the bus, the bus routes, and no service holds a reference to any other service.
  • Air traffic control, ride-hailing dispatch, and the coordination layer in a lot of game engines all lean on this idea.

Here is the runtime flow for the chat case, so you can see who talks to whom:

sequenceDiagram participant Alice participant ChatRoom participant Bob participant Carol Alice->>ChatRoom: send("hey", Alice) ChatRoom->>ChatRoom: apply rules (filter, mute check) ChatRoom->>Bob: receive("hey", "Alice") ChatRoom->>Carol: receive("hey", "Alice")
Alice sends exactly once and never names Bob or Carol. The room applies the rules in the middle, then fans the message out. If Bob leaves the room, Alice's code does not change at all, because she never referenced him.
From the interviewer's chair

When you draw this, draw the ugly version first: five users with twenty arrows between them. Let it look like a hairball on the board. Then collapse it into a star with the mediator in the center. That before-and-after sells the pattern in about ten seconds, and it tells me you understand why it exists, not just what it is.

6When to use it, and when not to#

Use it when:

  • You have a set of objects with many-to-many communication and the wiring is getting hairy.
  • The interaction logic between objects is complex and you want it in one place you can read and test.
  • You want to reuse a colleague in a different context by handing it a different mediator.

Skip it when:

  • The communication is simple or one-directional. Two objects talking? Let them talk. A mediator there is ceremony for nothing.
  • A natural hub-and-spoke already exists. If object A already owns B, C, and D, you may have your mediator without naming it.
Reach for it
Mediator
Shape
Many peers, bidirectional, rule-heavy.
Logic
Central object makes routing and policy decisions.
Coupling
Peers know only the hub (n edges).
Risk
Hub can grow into a god object.
Skip it
Direct calls
Shape
Two or three objects, simple or one-way.
Logic
No shared rules worth centralizing.
Coupling
Already low, nothing to untangle.
Risk
A mediator here is pure overhead.

The honest warning, and say it before you are asked: the mediator can swell into a god object.

  • All the logic that moved out of the colleagues has to go somewhere, and it piles up in the middle.
  • If your ChatRoom grows to 800 lines and knows every quirk of every colleague, you have traded a coupling problem for a complexity problem.
  • That is a real tradeoff, not a free lunch.
  • When the mediator gets fat, split it or introduce sub-mediators.
Red flag that fails you here

Pitching Mediator as a clean win with no downside. Every reviewer who has shipped one has watched the hub turn into the most feared file in the repo. If you cannot name the god-object risk on your own, I read it as pattern-from-a-book, not pattern-from-experience.

7Common mistakes#

Letting colleagues keep direct references to each other.

  • This is the big one.
  • If User still holds a pointer to another User "just for this one case," you have broken the pattern and kept all the coupling.
  • The whole deal is that colleagues only know the mediator.

Turning the mediator into a dumping ground.

  • The mediator should coordinate, not absorb every piece of business logic in the system.
  • If it is doing validation, persistence, logging, and routing all inline, pull those into separate collaborators that the mediator calls.

Confusing it with Observer.

  • They look similar because both decouple senders from receivers.
  • Observer is one-to-many broadcast with no coordination logic: publishers do not care who listens.
  • Mediator centralizes bidirectional coordination, with real decision-making in the middle.
  • The test: if your central object makes routing decisions and enforces rules, it is a mediator. If it is just a notification firehose, it is observer.

Leaking colleague state through the notify signature.

  • Keep the notify/send signature tight.
  • The mediator should figure out what to do from the sender and a small event.
  • If every colleague passes its full internal state up, your encapsulation is leaking.

8Where it shows up in interviews#

This pattern comes up constantly in design-a-chat-application and design-a-messaging-system prompts.

  • The moment you say "users send messages," the follow-up is "how do they find each other?"
  • The right answer is a chat room mediator, not users holding each other's references.

It also shows up anywhere a central controller coordinates independent units.

  • Air traffic control and elevator-dispatch problems.
  • UI-heavy prompts (design a form, design a smart home control panel) where components react to each other.

What interviewers are actually checking:

  • Can you spot the n-squared coupling and name the fix?
  • Can you put the coordination logic in the right place?
  • Can you articulate the god-object tradeoff without being asked? Bring that up yourself. It shows you have shipped this and felt the pain.

Draw the mesh, let it look ugly, then collapse it into a star. That one move sells the whole pattern.

Follow-ups the interviewer will ask (and your answers)
"How is this different from Observer? They both decouple sender and receiver."
Observer is one-to-many broadcast with no decision-making: the subject fires an event and does not care who listens or what they do with it. Mediator centralizes bidirectional coordination and makes real routing and policy decisions in the middle. If the central object enforces rules (filter, mute, enable/disable), it is a mediator. If it just fans out notifications, it is observer.
"Your ChatRoom is now 800 lines. Did the pattern fail?"
No, it did its job and then outgrew a single class. The logic had to land somewhere, and it piled up in the hub. The fix is to split the mediator: pull profanity, persistence, and routing into separate collaborators the mediator calls, or introduce sub-mediators per concern. The pattern localizes complexity so you can see it, which is exactly what lets you refactor it safely.
"A colleague needs to react to just one specific peer. Can it hold a reference to that peer?"
No, that reintroduces the coupling the pattern removes. The colleague reports its event to the mediator, and the mediator, which already knows every colleague, decides to call that one specific peer. The targeting logic lives in the hub, not in the colleague.
"Doesn't routing everything through one object create a bottleneck or single point of failure?"
At the object-design level it is a coordination point, not a scaling bottleneck: the calls are in-process method calls. The real risk is maintainability (the god object), not throughput. If you push this to a distributed message bus, that is the same shape at scale, and then you address availability by clustering and partitioning the bus, which is a system-design concern rather than a class-design one.
"How do colleagues register with the mediator without the mediator knowing their concrete types?"
Through the `Colleague` interface or abstract base. The mediator holds a list of the abstract type and calls methods declared there (`receive`, `onChange`). It never switches on concrete classes. That is Dependency Inversion: both sides depend on the abstraction, so you can add a new colleague type without touching the mediator's type assumptions.

What you can now explain in the room

  • Why n peers talking directly costs n-squared edges, and how the hub cuts it to n.
  • The four roles (Mediator, ConcreteMediator, Colleague, ConcreteColleague) and which one holds the brains.
  • Where interaction rules belong and why changing one rule touches exactly one method.
  • How Mediator differs from Observer, and the test for telling them apart.
  • The god-object tradeoff and when to split the mediator or add sub-mediators.
  • When to skip the pattern entirely because the coupling is already low.

Next, look at the Observer pattern (its close cousin) and the Facade pattern (also a "single front door," but for a subsystem rather than peer coordination). The chat-application LLD problem is the best place to practice all three together.

Get a prep plan built around your Mediator Pattern interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Mediator Pattern prep plan is on the way.