Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Design Patterns (GoF and modern)  Module 5 – Behavioral patterns: state and communication

Mediator: centralise many-to-many talk

Mediator puts the rules between objects in one coordinator: a checkout form and a chat room in code, and how it differs from Observer, Facade and an event bus.

  • Intermediate
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, Java 25, Go 1.27.1, Clang 21.0.0 and Rust 1.99.0
  • By MySmartCoPilot
Free core, Pro depth: 9 of 25 minutes free
Free: The problem: a web of references · The roles · A checkout form in Python · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 16 minutes.

What you will learn

  • Replace a web of object-to-object calls with a mediator that coordinates colleagues
  • Implement a form mediator and a chat-room mediator
  • Split a growing mediator by use case without changing its behaviour
  • Compare Mediator with Observer, Facade and an event bus

Before you start

What you’ll learn · Before you start

What you will learn

  • Replace a web of object-to-object calls with a mediator that coordinates colleagues
  • Implement a form mediator and a chat-room mediator
  • Split a growing mediator by use case without changing its behaviour
  • Compare Mediator with Observer, Facade and an event bus

Before you start

On this page

Mediator is a behavioural design pattern that moves the rules about how a group of objects interact into one object, the mediator. The objects it coordinates, its colleagues, stop referring to each other. Each one tells the mediator when something happens to it, and the mediator decides what the others should do, so a web of many-to-many calls becomes a star of one-to-many ones. In the Gang of Four catalogue the mediator encapsulates how a set of objects interact: they stay loosely coupled, and the interaction can change without changing them. Forms and dialog boxes, chat rooms, booking desks and workflow coordinators are its classic uses.

The problem: a web of references

Picture a checkout form with four widgets: an address field, a payment choice, a coupon field and a Pay button. Two rules connect them. Choosing cash on delivery disables the coupon field, and Pay works only when the whole form is valid. Written directly, the payment widget disables the coupon field itself, each field enables or disables the button, and the button asks every field whether it is valid.

Four checkout widgets linked to each other in six pairs, then the same widgets each linked only to a CheckoutForm mediator.Without a mediator: every widget refers to the othersWith a mediator: each widget knows only the formAddressPaymentCouponPay buttonCheckoutForm (the mediator)holds every rule between widgetsAddressPaymentCouponPay button

From a web of references to a star

Text description of the diagram

The diagram has two parts, one above the other.

  1. "Without a mediator": four widgets, Address, Payment, Coupon and Pay button, are drawn in a square. Every pair is joined by a line with arrows at both ends, six lines in all, because each widget refers to every other widget and calls it. That is 12 one-way references between 4 widgets.
  2. "With a mediator": the same four widgets are each joined by one two-way line to a single box, CheckoutForm, the mediator, which holds every rule between the widgets. No line joins two widgets.

With n widgets the first design can need n × (n − 1) references, which is 90 for 10 widgets, while the second needs 2n, which is 20.

Every widget now knows the others. Four widgets can need 12 one-way references, and ten widgets 90. No widget can be reused on a form that lacks the others, one rule is spread across several classes, and adding a widget means editing several existing ones. The star needs one reference from each widget to the form and one back: 8 for four widgets, 20 for ten.

The roles

  • The mediator, here CheckoutForm, creates or receives its colleagues, hears about every change and applies each rule that involves more than one of them.
  • The colleagues, here the widgets, know only their mediator. A widget tells the form “I changed”; it never calls another widget.
  • The messages run both ways: colleagues report to the mediator, and the mediator tells colleagues what to do, such as “disable yourself” or “clear your text”.

A checkout form in Python

A checkout form whose widgets never talk to each other Python · checkout/checkout.py
"""Mediator: a checkout form whose widgets never talk to each other. Each widget tells the form when the user changes
it, and the form, the mediator, applies every rule that involves more than one widget."""


class Widget:
    """A colleague: it knows only its mediator."""

    def __init__(self, form):
        self.form = form
        self.enabled = True

    def changed(self):
        self.form.widget_changed(self)


class AddressField(Widget):
    def __init__(self, form):
        super().__init__(form)
        self.text = ""

    def type(self, text):
        self.text = text
        self.changed()

    def valid(self):
        return len(self.text.strip()) >= 5


class PaymentChoice(Widget):
    def __init__(self, form):
        super().__init__(form)
        self.method = None  # "card" or "cash on delivery"

    def choose(self, method):
        self.method = method
        self.changed()


class CouponField(Widget):
    CODES = {"SAVE10"}

    def __init__(self, form):
        super().__init__(form)
        self.code = ""

    def enter(self, code):
        if self.enabled:  # a disabled field ignores typing
            self.code = code
            self.changed()

    def valid(self):
        return self.code == "" or self.code in self.CODES


class PayButton(Widget):
    def __init__(self, form):
        super().__init__(form)
        self.enabled = False


class CheckoutForm:
    """The mediator: it creates the widgets, hears about every change and applies the rules between them."""

    def __init__(self):
        self.address = AddressField(self)
        self.payment = PaymentChoice(self)
        self.coupon = CouponField(self)
        self.pay = PayButton(self)

    def widget_changed(self, widget):
        if widget is self.payment:
            cash = self.payment.method == "cash on delivery"
            self.coupon.enabled = not cash  # rule 1: no coupons with cash on delivery
            if cash:
                self.coupon.code = ""
        # rule 2: Pay only when the whole form is valid
        self.pay.enabled = self.address.valid() and self.payment.method is not None and self.coupon.valid()

    def show(self, step):
        coupon = (self.coupon.code or "empty") if self.coupon.enabled else "disabled"
        print(f"{step:30} coupon {coupon:9} pay {'ON' if self.pay.enabled else 'off'}")


form = CheckoutForm()
form.show("(the form opens)")
form.address.type("12 Lake Road")
form.show('type the address')
form.payment.choose("card")
form.show("choose card")
form.coupon.enter("SAVE1O")
form.show('type coupon "SAVE1O" (a typo)')
form.coupon.enter("SAVE10")
form.show('fix it to "SAVE10"')
form.payment.choose("cash on delivery")
form.show("choose cash on delivery")
form.coupon.enter("SAVE10")
form.show("try the coupon again")
form.address.type("")
form.show("clear the address")

widgets = [form.address, form.payment, form.coupon, form.pay]
links = [(type(w).__name__, name) for w in widgets for name, value in vars(w).items() if isinstance(value, Widget)]
print("references from one widget to another:", links or "none")

Output

(the form opens)               coupon empty     pay off
type the address               coupon empty     pay off
choose card                    coupon empty     pay ON
type coupon "SAVE1O" (a typo)  coupon SAVE1O    pay off
fix it to "SAVE10"             coupon SAVE10    pay ON
choose cash on delivery        coupon disabled  pay ON
try the coupon again           coupon disabled  pay ON
clear the address              coupon disabled  pay off
references from one widget to another: none

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 checkout.py

Both rules live in one method, widget_changed(). The widget classes stay tiny: a coupon field knows how to hold a code and whether the code is valid, and nothing about payment methods. The script plays a user. Choosing a card enables Pay, a mistyped coupon disables it until the code is fixed, and choosing cash on delivery disables the coupon field and clears the code while Pay stays enabled. Trying the coupon again changes nothing, because a disabled field ignores typing, and clearing the address turns Pay off. The last line checks the promise of the pattern: no widget holds a reference to another widget, only to the form.

Mediator, Observer, Facade and an event bus

Four patterns get confused with each other here, because all four put something between objects.

  • An Observer subject announces its own changes to subscribers it does not know. Information flows one way.
  • A mediator knows its colleagues and coordinates them in both directions.
  • A Facade gives callers outside a subsystem one simpler interface. The classes behind it never call it.
  • An event bus delivers named events from any publisher to any subscriber, and it knows no rules.
Four ways to put something between objects
Criterion ObserverMediatorFacadeEvent bus
Who talks to whom One subject to many observersColleagues to the mediator and backOutside callers to the facadeAny publisher to any subscriber
Who holds the rules Each observer, for itselfThe mediatorThe subsystem behind itNobody: subscribers decide alone
Does the middle know the others Only as a list of callbacksYes, each colleagueYes, the subsystem classesNo, only event names
Typical use Displays of one changing valueForms, chat rooms, booking desksA simple entrance to a libraryLoosely related modules of an application

The patterns also combine. The widgets above report to their mediator through a callback, a small Observer link, and Martin Fowler describes an Event Aggregator as “a single source of events for many objects”: an event bus is a mediator that has given its rules away to its subscribers.

Exercise: a booking desk

Exercise · Medium · Python

A booking desk that coordinates a room, calendars and a notifier

booking.py has three colleagues that know nothing about each other: a Room (its capacity and the times it is reserved), a Calendar (each person's meetings) and a Notifier (the messages it has sent). Times are whole hours, and a meeting from start to end overlaps another when start < other_end and other_start < end.

Finish the mediator, BookingDesk:

  • book(title, start, end, attendees) books the meeting only if the room can seat everyone, the room is available for the whole time and every attendee is available. Then it reserves the room, adds the meeting to every attendee's calendar and sends each attendee "Invited: <title> <start>-<end>". It returns True when it booked the meeting, and False when it refused it (and then changes nothing).
  • cancel(title) releases the room, removes the meeting from the calendars and sends each attendee "Cancelled: <title>". It returns False for a title it does not know.

The colleagues must keep knowing nothing about each other or the desk: every rule that involves two of them belongs in BookingDesk.

Starter code · booking.py

"""Three colleagues that know nothing about each other, and the mediator that coordinates them."""


def overlaps(a_start, a_end, b_start, b_end):
    return a_start < b_end and b_start < a_end


class Room:
    def __init__(self, name, capacity):
        self.name, self.capacity = name, capacity
        self.reservations = {}  # title -> (start, end)

    def is_free(self, start, end):
        return not any(overlaps(start, end, s, e) for s, e in self.reservations.values())

    def reserve(self, title, start, end):
        self.reservations[title] = (start, end)

    def release(self, title):
        self.reservations.pop(title, None)


class Calendar:
    def __init__(self):
        self.meetings = {}  # person -> {title: (start, end)}

    def is_free(self, person, start, end):
        return not any(overlaps(start, end, s, e) for s, e in self.meetings.get(person, {}).values())

    def add(self, person, title, start, end):
        self.meetings.setdefault(person, {})[title] = (start, end)

    def remove(self, person, title):
        self.meetings.get(person, {}).pop(title, None)


class Notifier:
    def __init__(self):
        self.sent = []  # (person, text)

    def send(self, person, text):
        self.sent.append((person, text))


class BookingDesk:
    """The mediator: every rule that involves more than one colleague lives here."""

    def __init__(self, room, calendar, notifier):
        self.room, self.calendar, self.notifier = room, calendar, notifier
        self.attendees = {}  # title -> attendees

    def book(self, title, start, end, attendees):
        # Replace these lines: check capacity, the room and every attendee first; then reserve, add and invite.
        self.room.reserve(title, start, end)
        return True

    def cancel(self, title):
        # Replace this line: release the room, remove the meeting from the calendars and tell the attendees.
        return False
The sample tests · test_booking.py
from booking import BookingDesk, Calendar, Notifier, Room


def desk(capacity=4):
    return BookingDesk(Room("Banyan", capacity), Calendar(), Notifier())


def test_three_meetings_are_booked_and_invitations_sent():
    """books three meetings that do not overlap and invites every attendee"""
    d = desk()
    assert d.book("Stand-up", 9, 10, ["Asha", "Ravi"])
    assert d.book("Design review", 10, 12, ["Asha", "Kiran"])
    assert d.book("Retro", 15, 16, ["Ravi"])
    assert d.room.reservations == {"Stand-up": (9, 10), "Design review": (10, 12), "Retro": (15, 16)}
    assert ("Kiran", "Invited: Design review 10-12") in d.notifier.sent
    assert len(d.notifier.sent) == 5


def test_a_meeting_in_a_taken_room_is_refused():
    """refuses a meeting that overlaps a booking of the room, and changes nothing"""
    d = desk()
    assert d.book("Stand-up", 9, 10, ["Asha"])
    assert not d.book("Planning", 9, 11, ["Ravi"])
    assert "Planning" not in d.room.reservations
    assert d.calendar.is_free("Ravi", 9, 11)
    assert d.notifier.sent == [("Asha", "Invited: Stand-up 9-10")]


def test_a_busy_attendee_blocks_the_meeting():
    """refuses a meeting when one attendee is already in another meeting at that time"""
    d = desk()
    d.calendar.add("Kiran", "Dentist", 14, 15)
    assert not d.book("One-to-one", 14, 15, ["Asha", "Kiran"])
    assert d.room.is_free(14, 15)
    assert d.calendar.is_free("Asha", 14, 15)


def test_the_room_must_seat_everyone():
    """refuses a meeting with more attendees than the room has seats"""
    d = desk(capacity=2)
    assert not d.book("All hands", 11, 12, ["Asha", "Ravi", "Kiran"])
    assert d.notifier.sent == []


def test_cancel_releases_the_room_and_tells_attendees():
    """cancels a meeting: the room and the calendars are free again and every attendee is told"""
    d = desk()
    d.book("Design review", 10, 12, ["Asha", "Kiran"])
    assert d.cancel("Design review")
    assert d.room.is_free(10, 12)
    assert d.calendar.is_free("Asha", 10, 12) and d.calendar.is_free("Kiran", 10, 12)
    assert d.notifier.sent[-2:] == [("Asha", "Cancelled: Design review"), ("Kiran", "Cancelled: Design review")]
    assert not d.cancel("Design review")


def test_colleagues_do_not_refer_to_each_other():
    """keeps every rule in the desk: no colleague holds another colleague or the desk"""
    d = desk()
    d.book("Stand-up", 9, 10, ["Asha"])
    for colleague in (d.room, d.calendar, d.notifier):
        for value in vars(colleague).values():
            assert not isinstance(value, (Room, Calendar, Notifier, BookingDesk))
A hint

Write book() as checks first, actions second. Return False as soon as one check fails, before anything is reserved, so a refused meeting leaves no trace. Keep a dictionary from each title to its attendees, so that cancel() knows whom to tell.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 In the checkout example the user chooses cash on delivery. Which object disables the coupon field?

    Choose one answer.

    Show the answer to question 1

    Answer: The CheckoutForm, because the rule involves two widgets

    The payment widget only tells the form that it changed. The form holds every rule that involves more than one widget, so it is the form that disables the coupon field and clears its code.

  2. Question 2 of 4 Ten widgets each refer to every other widget. How many one-way references is that?

    Type a number.

    Show the answer to question 2

    Answer: 90 references

    Each of the 10 widgets refers to the 9 others: 10 × 9 = 90. With a mediator, each widget refers to the mediator and the mediator refers to each widget, 2 × 10 = 20 references, and the rules sit in one class.

  3. Question 3 of 4 A payments class gives the rest of an application one method, pay(order), which calls a fraud check, a payment gateway and a ledger. Why is this a facade rather than a mediator?

    Choose one answer.

    Show the answer to question 3

    Answer: It simplifies a subsystem for callers outside it, and the fraud check, gateway and ledger do not know it exists

    A facade is a simpler entrance for outsiders, and the classes behind it never call it. A mediator's colleagues do call it: they report their changes to it, and it tells the others what to do.

  4. Question 4 of 4 Which sentence describes an event bus rather than a mediator?

    Choose one answer.

    Show the answer to question 4

    Answer: It delivers each named event to whoever subscribed to that name and knows none of the rules

    An event bus routes events by name and leaves every decision to its subscribers. A mediator holds the rules between its colleagues, so reading it tells you what happens when one of them changes.

A chat room with mute and private messages, in six languages Pro: opens with a Pro, Premium or Ultimate pass

A chat room is the textbook mediator: users send everything to the room, and the room decides who receives it. Here it gains rules for ignoring, muting and private messages, a check that no user ever holds another user, and the same room in five more languages.

In this part:

  • The room’s rules in one place
  • Checking that no user holds another user
  • The same room in Java, TypeScript, Go, C++ and Rust

4 runnable examples (C++, Go, Java, Python, Rust, TypeScript, languages_session.py, session.py) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Keeping a mediator small, measured on a growing form Pro: opens with a Pro, Premium or Ultimate pass

A mediator collects rules, and on a busy form it can become the one class that every change touches. This part grows the checkout to seven widgets and five rules, splits its mediator by use case, and proves with 2,000 random user actions that the split changed nothing.

In this part:

  • How a mediator turns into a god class, with numbers
  • Splitting by use case, with a differential test
  • What a colleague may know: the Law of Demeter
  • Mediator or event bus, compared

1 runnable example (god_mediator.py, growth.py, split_mediators.py, widgets.py) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    How is the Mediator pattern different from the Observer pattern?

    Observer is one-way broadcasting: a subject announces its own changes to observers it knows only as callbacks, and each observer decides for itself what to do. Mediator is two-way coordination: colleagues report their changes to the mediator, and the mediator, which knows every colleague, decides what the others must do, so the rules between the objects live in one place. In a checkout form, a price label observing a cart is Observer; a form object that disables the coupon field when cash on delivery is chosen is Mediator. The two often appear together, because colleagues can report to their mediator through an Observer-style callback.

  2. Mid to senior

    A checkout page's mediator has grown to 1,800 lines with about 140 branches, and every feature touches it. How do you split it without changing what the page does, and how do you prove that nothing changed?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    In a ticket-booking service, seats are held, then paid for, then the ticket is issued, and a failed payment must release the seats. One team wants a mediator for the flow, the other an event bus between the seat, payment and ticket modules. Which do you choose, and how do you defend it?

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A chat product's rooms are in-process mediators: each room object holds its members and delivers messages. The product now needs 50,000 connected users spread over 20 servers, with members of one room on different servers. What breaks, and what replaces the in-process mediator?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A mediator turns many-to-many references between colleagues into one-to-many: each colleague knows only the mediator, and the rules between colleagues live in one place.
  • Colleagues report changes; the mediator applies the rules and tells the others what to do, so each colleague stays small and reusable.
  • Observer announces one subject’s changes, a mediator coordinates peers in both directions, a facade simplifies a subsystem for outsiders, and an event bus routes events without rules.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.