Skip to content

Mediator

Replacing many-to-many connections with a hub that coordinates them — and how the hub becomes the god object the pattern was meant to prevent.

Advanced17 minUpdated 8 August 2026

Mediator replaces direct connections between many objects with a single object that coordinates them. Each participant knows only the mediator; the mediator knows the interaction rules.

It is the pattern with the highest risk of making things worse, because the coupling it removes from the participants has to go somewhere, and where it goes is one class that now knows everything.

The problem

A booking form: choosing a date filters available rooms, choosing a room updates the price, applying a discount code re-checks eligibility against the room and date, and changing occupancy invalidates the room selection.

// Every control knows several others
datePicker.onChange = () => { roomList.filterBy(datePicker.value); priceLabel.refresh(); };
roomList.onSelect  = () => { priceLabel.refresh(); discountField.revalidate(); };
occupancy.onChange = () => { roomList.filterBy(...); roomList.clearSelection(); priceLabel.refresh(); };

Five controls with mutual awareness approach twenty possible connections. Nobody can be tested alone, nobody can be reused, and adding a sixth control means editing the five that must react to it.

Conceptual diagramDateRoomsPriceOccupancyDiscountDateBookingFormmediatorRoomsPriceOccupancyDiscount
Diagram, as text
  • Date leads to Rooms
  • Rooms leads to Price
  • Date leads to Price
  • Occupancy leads to Rooms
  • Discount leads to Price
  • Discount leads to Rooms
  • Date leads to BookingForm mediator
  • Rooms leads to BookingForm mediator
  • Price leads to BookingForm mediator
  • Occupancy leads to BookingForm mediator
  • Discount leads to BookingForm mediator
  • BookingForm mediator leads to Date
  • BookingForm mediator leads to Rooms
  • BookingForm mediator leads to Price

Each control now reports what happened to it and does what it is told. The interaction rules move from the edges to the centre, where they can be read as a set.

What it genuinely buys

Participants become reusable and testable. A date picker that knows nothing about rooms can be used on any form and tested with no other component present.

The rules exist in one readable place. "When occupancy changes, clear the room selection and refresh the price" is a line in the mediator rather than an emergent property of five event handlers.

Adding a participant is additive. The new control reports to the mediator; the mediator decides who cares. The existing five are untouched.

How it goes wrong

The mediator absorbs every rule, and rules keep arriving. The class that started as coordination becomes:

  • A god object. Every feature edits it, so every feature can break every other. It becomes the file with the most merge conflicts in the repository.
  • A dependency hub. It knows all participants and all rules, so it cannot be tested without constructing everything.
  • A dumping ground. Logic that belongs in a participant ends up in the mediator because the mediator can see everything and the participant cannot.

The pattern moved the complexity; it did not remove it. That is a legitimate trade when many-to-many coupling was genuinely the problem, and pure loss when it was not.

Mediator versus Observer

They solve overlapping problems and differ in an important way.

MediatorObserver
Knowledge of rulescentralised in the mediatordistributed across subscribers
Who decides reactionsthe mediatoreach subscriber, independently
Readabilityrules are in one placerules must be gathered by searching
Couplingparticipants → mediatorpublisher → nothing
Best forinteractions that must coordinatereactions that are independent

The distinguishing question: do the reactions need to know about each other? If changing occupancy must clear the room and then refresh the price with the cleared selection, that is coordination and Observer will express it badly — you would be relying on subscriber ordering, which is the mistake that topic warns against. If the reactions are genuinely independent, Observer keeps them decoupled and Mediator centralises for nothing.

Keeping it from rotting

Bound its scope. A mediator per form, per screen, per workflow — not per application. BookingForm is a mediator; UiCoordinator is a god object with a plan.

Keep it stateless where possible. A mediator holding its own copy of participants' state has two sources of truth, and they will disagree.

Move computation out. The mediator decides who reacts; components compute.

Watch its dependency list. Ten participants is a signal that the screen does too much, and the fix is usually to split the screen rather than the mediator.

Check your understanding

Common mistakes

One mediator for a whole application. Bound it to a form, screen or workflow.

Letting domain logic accumulate in it. It should decide who reacts, not compute the reaction.

Duplicating participants' state inside it. Two sources of truth that will disagree.

Using it where reactions are independent. Observer keeps them decoupled; a mediator centralises for no benefit.

Participants that also talk directly. A partial mediator gives you both the hub and the mesh.

Growing past a handful of participants. Usually a signal the screen does too much, not that the mediator needs splitting.

Testing only through the mediator. If participants cannot be tested alone, the decoupling never happened.

Practice

A checkout screen has 11 components with direct references to one another. Propose a refactor, and say how you would avoid simply relocating the mess into one class.

Write your own answer first, on paper or in a note. Then compare.

Show a hint

Eleven participants is itself a signal. Ask whether it is one screen or several.

Show a strong answer

First, question whether this is one mediator's job. Eleven mutually aware components is not primarily a coupling problem, it is a screen doing too much. I would look for clusters before writing any coordinator: address entry, payment details, and order summary are usually three groups whose members interact heavily inside the group and barely across it. Building one CheckoutMediator over all eleven produces exactly the god object the question is worried about, and it would be my own doing rather than an inherited problem.

So the shape I would aim for: two or three bounded mediators, each owning a cluster, with a thin coordination between them — usually just "address changed, recompute totals". If the cross-cluster interactions turn out to be more than two or three, that is evidence the clusters are wrong and I would re-draw them.

How I would find the clusters, rather than guessing: list every existing direct reference as edges and look at where the graph is dense. Components with many mutual references belong together; components connected by a single edge are the seams. This is a ten-minute exercise on paper and it beats intuition, because the obvious visual grouping on screen often does not match the interaction graph.

Guarding against relocating the mess:

  • A hard rule that the mediator computes nothing. No arithmetic, no eligibility logic, no formatting. If a rule needs a calculation, it calls a component or a domain service. This is the single most effective constraint, and it is easy to check in review.
  • The mediator holds no state that a participant owns. It may hold coordination state — "we are mid-submission" — but not the selected address.
  • Every participant keeps a test with no mediator present. If a component cannot be constructed and exercised alone, it is still coupled and the refactor has not achieved its goal.
  • A size budget, agreed up front. If a mediator passes a few hundred lines, that is a prompt to re-examine the clusters rather than to keep going.

Sequencing: convert one cluster at a time, leaving the others on direct references. Each conversion is independently valuable and reviewable, and if the first one goes badly, the approach can be reconsidered having spent little.

What I would watch for afterwards: the mediator becoming the file with the most changes per sprint. That metric is the early warning that it is drifting back towards a god object, and it is visible in the repository history without anyone having to notice by feel.

A backend team proposes a mediator to coordinate 6 microservices during order fulfilment, arguing it centralises the workflow. Evaluate, and say what you would call the thing they should build.

Write your own answer first, on paper or in a note. Then compare.

Show a hint

The in-process pattern and the distributed problem share a shape but not a failure model.

Show a strong answer

The instinct is right and the name is wrong, and the difference is not pedantic. Centralising the workflow is genuinely better than six services each knowing what the others do — that is the choreography-versus-orchestration argument, and orchestration wins for workflows anyone needs to reason about or debug. But a Mediator is an in-process object coordinating collaborators that cannot fail independently, and none of that holds across six services.

What changes once the participants are remote:

  • Every call can fail, time out, or succeed without the caller learning. An in-process mediator's calls either return or throw. A remote one has a third outcome, and it is the common one in incidents.
  • The workflow outlives the process. A deploy mid-fulfilment must not lose the order. That means the coordinator's state has to be persisted after every step, which an in-memory mediator never contemplates.
  • Steps need compensation, not undo. If payment succeeds and the warehouse rejects, something must reverse the payment — and that reversal is a business action with its own failure modes.
  • Duplicate delivery is a certainty, so every step must be idempotent.

What they should build: a process manager (an orchestrator) driving a saga — which has its own topic in this module. Concretely, that means a persisted workflow instance with an explicit state machine, a compensating action defined per step, idempotency keys on every outbound command, timeouts with escalation for steps that never answer, and a way for an operator to inspect and resume a stuck instance.

The corpus covers the saga mechanics and the choreography-versus-orchestration trade in high-level design; the point here is that the pattern name they reached for imports the wrong set of assumptions. Calling it a mediator invites an implementation with in-memory state and unguarded calls, and that implementation will look correct in every test and lose orders in production.

What I would keep from their proposal: the centralisation, the single readable definition of the workflow, and the principle that the six services should not know about each other. Those are the valuable parts and they survive the renaming.

The one thing I would push back on beyond the name: make sure the orchestrator does not become the god object in the distributed form. It should sequence steps and hold no business rules — pricing, eligibility and inventory decisions stay in the services that own them. That is the same discipline the in-process pattern needs, and it matters more here because the blast radius is larger.

Key takeaways

  • Mediator replaces many-to-many connections with a hub each participant reports to.
  • It relocates coupling rather than removing it, which is a good trade only when many-to-many was genuinely the problem.
  • Participants become reusable and testable alone, and the interaction rules become readable as a set.
  • Choose it over Observer when reactions must coordinate; choose Observer when they are genuinely independent.
  • Bound its scope to a form, screen or workflow. One mediator per application is a god object with a plan.
  • It coordinates, it does not compute. Arithmetic and business rules in a mediator are the first sign of rot.
  • Distributed coordination is a process manager driving a saga, not a mediator — remote participants fail independently and the workflow must be persisted.

Further reading

  • Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994).
  • Gregor Hohpe and Bobby Woolf, Enterprise Integration Patterns (Addison-Wesley, 2003) — process managers and the distributed relatives of this pattern.
  • Martin Fowler, Patterns of Enterprise Application Architecture (Addison-Wesley, 2002).
  • Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004) — breaking dependency tangles incrementally.
  • Kent Beck, Implementation Patterns (Addison-Wesley, 2007) — cohesion and the size of a coordinating object.

Check yourself

3 questions in this lesson. Answer each once to complete it.

No problem practices this lesson yet. Browse the practice problems

Next lessonMemento
Lessons in this track