Skip to content
KAVRIQ
Interview contents

Mediator Pattern

Estimated time: 20 minutes

Goal: Understand the intent, participants, structure, common use-cases, and a small code example (Python + Rust) so you can explain the pattern in an interview and implement a basic mediator in production code.


1. Motivation / When to use

When many objects interact with each other, the interaction logic can become complex and tightly coupled. The Mediator Pattern centralizes this interaction into a single object — the mediator — reducing direct dependencies between colleagues and making the system easier to maintain and evolve.

Common scenarios:

  • Complex communication protocols between components (UI widgets, subsystems).
  • Preventing a many-to-many object relationship explosion.
  • Encapsulating control logic that doesn’t belong to any single component.

2. Intent (one-liner)

Define an object that encapsulates how a set of objects interact. Promote loose coupling by keeping objects from referring to each other explicitly, and let the mediator handle communications.


3. Participants

  • Mediator (interface / abstract): Declares methods for communication between colleagues.
  • ConcreteMediator: Implements mediator behavior, knows and maintains references to colleagues, coordinates interaction.
  • Colleague (interface / abstract): Declares an interface for components that communicate through the mediator.
  • ConcreteColleague: Implements behavior and communicates via the mediator instead of calling other colleagues directly.

4. Structure (UML-like)

+---------------+ uses +------------------+
| ConcreteCol- |<------------------>| Mediator |
| lague A | | (ConcreteMediator)|
+---------------+ +------------------+
^ ^
| |
+---------------+ +------------------+
| ConcreteCol- | | other colleagues |
| lague B | +------------------+
+---------------+

Colleagues send messages to the mediator; the mediator decides which colleague(s) should receive the information and calls them.


5. Simple example (Python)

from __future__ import annotations
from typing import Protocol
class Mediator(Protocol):
def notify(self, sender: object, event: str) -> None: ...
class ConcreteMediator:
def __init__(self):
self._col_a = None
self._col_b = None
def register_a(self, col):
self._col_a = col
def register_b(self, col):
self._col_b = col
def notify(self, sender, event: str) -> None:
if event == "A_done":
print("Mediator reacts on A_done and triggers B.action_b().")
self._col_b.action_b()
elif event == "B_done":
print("Mediator reacts on B_done and triggers A.action_a().")
self._col_a.action_a()
class ColleagueA:
def __init__(self, mediator: Mediator):
self._mediator = mediator
def action_a(self):
print("ColleagueA does A and notifies mediator")
self._mediator.notify(self, "A_done")
class ColleagueB:
def __init__(self, mediator: Mediator):
self._mediator = mediator
def action_b(self):
print("ColleagueB does B and notifies mediator")
self._mediator.notify(self, "B_done")
# Usage
m = ConcreteMediator()
a = ColleagueA(m)
b = ColleagueB(m)
m.register_a(a)
m.register_b(b)
# Start chain
a.action_a()

Notes: colleagues never call each other directly; they call mediator.notify(...) with events or messages.


6. Compact example (Rust)

// Simple mediator in Rust using trait objects
use std::rc::Rc;
use std::cell::RefCell;
trait Mediator {
fn notify(&self, sender: &str, event: &str);
}
struct ConcreteMediator {
a: RefCell<Option<Rc<dyn Colleague>>>,
b: RefCell<Option<Rc<dyn Colleague>>>,
}
impl ConcreteMediator {
fn new() -> Self {
Self { a: RefCell::new(None), b: RefCell::new(None) }
}
}
impl Mediator for ConcreteMediator {
fn notify(&self, sender: &str, event: &str) {
if sender == "A" && event == "A_done" {
if let Some(b) = &*self.b.borrow() {
b.receive("Mediator: triggered B")
}
}
}
}
trait Colleague {
fn receive(&self, message: &str);
}
struct ColleagueA { mediator: Rc<dyn Mediator> }
impl Colleague for ColleagueA {
fn receive(&self, message: &str) {
println!("ColleagueA received: {}", message);
}
}
// (Omitted: wiring up instances to keep snippet short for lesson)

Note: Rust example is intentionally compact; wiring requires Rc and borrowing. In production, prefer explicit ownership models and channels for asynchronous mediation.


7. Pros & Cons

Pros:

  • Reduces coupling between components.
  • Centralizes control logic and protocol decisions.
  • Easier to change interaction logic in one place.

Cons:

  • Mediator can become a God Object if it grows too complex.
  • Adds indirection, which can make flow harder to trace unless documented.

  • Observer: more decentralized; observers independently react to events.
  • Facade: simplifies interface but doesn’t manage bi-directional interactions between peers.
  • Event Bus / Pub-Sub: decouples senders and receivers via topics; good for large distributed systems.

9. Interview checklist (what to say in 2–3 minutes)

  1. State the intent: centralize interaction to reduce coupling.
  2. Describe participants: Mediator + Colleagues.
  3. Explain trade-offs: simpler coupling vs potential God Object.
  4. Give a short example: UI widgets notifying mediator which updates other widgets.
  5. End with alternatives: Observer and Pub-Sub.

10. Quick quiz (2 questions)

  1. Why might you choose an event bus over a mediator? (Answer: scalability and decentralization when many producers/consumers exist.)
  2. What is a primary risk when moving many responsibilities into the mediator? (Answer: it can become a God Object and hard to maintain.)

11. References / further reading

  • Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software
  • Martin Fowler — articles on Mediator / Event-driven architectures

Prepared for: interview-section/design-patterns — Mediator Pattern (20 min)