Mediator Pattern: Reduce Object Coupling

Updated 2026-03-11

TL;DR

The Mediator Pattern centralizes complex communication between objects by introducing a mediator object that handles all interactions. Instead of objects referencing each other directly (creating tight coupling), they communicate through the mediator, reducing dependencies and making the system easier to maintain and extend.

Prerequisites: Understanding of classes and objects, basic inheritance, interface/abstract class concepts, and familiarity with the Observer pattern. Knowledge of coupling and cohesion principles will help contextualize when to use this pattern.

After this topic: Implement the Mediator Pattern to decouple components that need to communicate, identify scenarios where direct object-to-object communication creates maintenance problems, and design systems where adding new components doesn’t require modifying existing ones.

Core Concept

What is the Mediator Pattern?

The Mediator Pattern is a behavioral design pattern that reduces chaotic dependencies between objects by making them communicate indirectly through a mediator object. Think of it like an air traffic control tower: planes don’t talk directly to each other (which would be dangerous and chaotic), they all communicate through the tower.

The Problem It Solves

When objects need to interact with multiple other objects, direct communication creates a web of dependencies. Each object needs references to many others, making the code:

  • Tightly coupled: Changes to one object affect many others
  • Hard to maintain: Adding new objects requires updating existing ones
  • Difficult to reuse: Objects can’t be used independently
  • Complex to understand: The interaction logic is scattered everywhere

How It Works

The pattern introduces a mediator object that encapsulates how objects interact. Components (called colleagues) only know about the mediator, not each other. When a colleague needs to communicate, it tells the mediator, which then coordinates with other colleagues.

Key participants:

  • Mediator: Defines the interface for communicating with colleagues
  • ConcreteMediator: Implements coordination logic and maintains references to colleagues
  • Colleague: Components that communicate through the mediator

When to Use It

  • Objects communicate in complex but well-defined ways
  • Reusing objects is difficult due to many dependencies
  • Behavior distributed among classes should be customizable without subclassing
  • You want to centralize control logic in one place

Real-World Analogies

  • Chat room: Users send messages to the room, which distributes them
  • Airport control tower: Coordinates all aircraft without them talking directly
  • GUI dialog boxes: Form fields notify a dialog manager of changes
  • Stock exchange: Buyers and sellers trade through the exchange, not directly

Visual Guide

Direct Communication (Without Mediator)

graph TD
    A[Component A] <--> B[Component B]
    A <--> C[Component C]
    A <--> D[Component D]
    B <--> C
    B <--> D
    C <--> D
    style A fill:#ff6b6b
    style B fill:#ff6b6b
    style C fill:#ff6b6b
    style D fill:#ff6b6b

Without a mediator, components have many direct dependencies (n(n-1)/2 connections for n components). This creates tight coupling and makes changes difficult.*

Mediator Pattern Structure

graph TD
    M[Mediator]
    A[Component A] --> M
    B[Component B] --> M
    C[Component C] --> M
    D[Component D] --> M
    M -.notifies.-> A
    M -.notifies.-> B
    M -.notifies.-> C
    M -.notifies.-> D
    style M fill:#4ecdc4
    style A fill:#95e1d3
    style B fill:#95e1d3
    style C fill:#95e1d3
    style D fill:#95e1d3

With a mediator, components only depend on the mediator (n connections). The mediator coordinates all interactions, reducing coupling.

Chat Room Example Flow

sequenceDiagram
    participant U1 as User 1
    participant CR as ChatRoom
    participant U2 as User 2
    participant U3 as User 3
    U1->>CR: send("Hello!")
    CR->>U2: receive("User 1: Hello!")
    CR->>U3: receive("User 1: Hello!")
    Note over CR: Mediator handles<br/>message distribution

Sequence diagram showing how the mediator (ChatRoom) coordinates communication between users without them knowing about each other.

Examples

Example 1: Chat Room System

Let’s build a chat room where users can send messages without knowing about other users.

from abc import ABC, abstractmethod
from typing import List

# Mediator interface
class ChatMediator(ABC):
    @abstractmethod
    def send_message(self, message: str, sender: 'User') -> None:
        pass
    
    @abstractmethod
    def add_user(self, user: 'User') -> None:
        pass

# Concrete Mediator
class ChatRoom(ChatMediator):
    def __init__(self, name: str):
        self.name = name
        self._users: List[User] = []
    
    def add_user(self, user: 'User') -> None:
        self._users.append(user)
        print(f"{user.name} joined {self.name}")
    
    def send_message(self, message: str, sender: 'User') -> None:
        # Mediator coordinates: send to all except sender
        for user in self._users:
            if user != sender:
                user.receive(f"{sender.name}: {message}")

# Colleague
class User:
    def __init__(self, name: str, mediator: ChatMediator):
        self.name = name
        self._mediator = mediator
        self._mediator.add_user(self)
    
    def send(self, message: str) -> None:
        print(f"{self.name} sends: {message}")
        self._mediator.send_message(message, self)
    
    def receive(self, message: str) -> None:
        print(f"{self.name} receives: {message}")

# Usage
chat_room = ChatRoom("Python Developers")

alice = User("Alice", chat_room)
bob = User("Bob", chat_room)
charlie = User("Charlie", chat_room)

alice.send("Hey everyone!")
bob.send("Hi Alice!")

Expected Output:

Alice joined Python Developers
Bob joined Python Developers
Charlie joined Python Developers
Alice sends: Hey everyone!
Bob receives: Alice: Hey everyone!
Charlie receives: Alice: Hey everyone!
Bob sends: Hi Alice!
Alice receives: Bob: Hi Alice!
Charlie receives: Bob: Hi Alice!

Key Points:

  • Users don’t hold references to other users
  • ChatRoom mediator handles all message routing
  • Adding new users doesn’t require changing existing user code
  • Communication logic is centralized in one place

Try it yourself: Extend the chat room to support private messages between two specific users.


Example 2: Air Traffic Control System

A more complex example showing how the mediator coordinates aircraft landing and takeoff.

from abc import ABC, abstractmethod
from enum import Enum
from typing import List, Optional

class FlightStatus(Enum):
    WAITING = "waiting"
    CLEARED = "cleared"
    LANDED = "landed"

# Mediator interface
class ATCMediator(ABC):
    @abstractmethod
    def request_landing(self, aircraft: 'Aircraft') -> bool:
        pass
    
    @abstractmethod
    def request_takeoff(self, aircraft: 'Aircraft') -> bool:
        pass
    
    @abstractmethod
    def notify_landed(self, aircraft: 'Aircraft') -> None:
        pass

# Concrete Mediator
class ControlTower(ATCMediator):
    def __init__(self, name: str):
        self.name = name
        self._runway_available = True
        self._waiting_queue: List[Aircraft] = []
    
    def request_landing(self, aircraft: 'Aircraft') -> bool:
        print(f"[{self.name}] {aircraft.call_sign} requests landing")
        
        if self._runway_available:
            self._runway_available = False
            print(f"[{self.name}] Cleared to land: {aircraft.call_sign}")
            return True
        else:
            print(f"[{self.name}] Runway occupied. {aircraft.call_sign} hold position")
            self._waiting_queue.append(aircraft)
            return False
    
    def request_takeoff(self, aircraft: 'Aircraft') -> bool:
        print(f"[{self.name}] {aircraft.call_sign} requests takeoff")
        
        if self._runway_available:
            self._runway_available = False
            print(f"[{self.name}] Cleared for takeoff: {aircraft.call_sign}")
            return True
        else:
            print(f"[{self.name}] Runway occupied. {aircraft.call_sign} wait")
            return False
    
    def notify_landed(self, aircraft: 'Aircraft') -> None:
        print(f"[{self.name}] {aircraft.call_sign} has landed")
        self._runway_available = True
        
        # Process waiting queue
        if self._waiting_queue:
            next_aircraft = self._waiting_queue.pop(0)
            print(f"[{self.name}] Clearing next aircraft: {next_aircraft.call_sign}")
            next_aircraft.receive_clearance()

# Colleague
class Aircraft:
    def __init__(self, call_sign: str, atc: ATCMediator):
        self.call_sign = call_sign
        self._atc = atc
        self.status = FlightStatus.WAITING
    
    def request_landing(self) -> None:
        cleared = self._atc.request_landing(self)
        if cleared:
            self.status = FlightStatus.CLEARED
            self.land()
    
    def land(self) -> None:
        print(f"[{self.call_sign}] Landing...")
        self.status = FlightStatus.LANDED
        self._atc.notify_landed(self)
    
    def receive_clearance(self) -> None:
        print(f"[{self.call_sign}] Received clearance")
        self.status = FlightStatus.CLEARED
        self.land()
    
    def request_takeoff(self) -> None:
        cleared = self._atc.request_takeoff(self)
        if cleared:
            self.takeoff()
    
    def takeoff(self) -> None:
        print(f"[{self.call_sign}] Taking off...")
        self._atc.notify_landed(self)  # Runway becomes available

# Usage
tower = ControlTower("JFK Tower")

flight1 = Aircraft("AA101", tower)
flight2 = Aircraft("UA202", tower)
flight3 = Aircraft("DL303", tower)

print("=== Scenario: Multiple landing requests ===")
flight1.request_landing()
flight2.request_landing()  # Will be queued
flight3.request_landing()  # Will be queued

Expected Output:

=== Scenario: Multiple landing requests ===
[JFK Tower] AA101 requests landing
[JFK Tower] Cleared to land: AA101
[AA101] Landing...
[JFK Tower] AA101 has landed
[JFK Tower] Clearing next aircraft: UA202
[UA202] Received clearance
[UA202] Landing...
[JFK Tower] UA202 has landed
[JFK Tower] Clearing next aircraft: DL303
[DL303] Received clearance
[DL303] Landing...
[JFK Tower] DL303 has landed
[JFK Tower] UA202 requests landing
[JFK Tower] Runway occupied. UA202 hold position
[JFK Tower] DL303 requests landing
[JFK Tower] Runway occupied. DL303 hold position

Key Points:

  • Aircraft don’t know about each other’s existence
  • ControlTower manages runway availability and queuing
  • Complex coordination logic is centralized
  • Easy to add features like priority handling or multiple runways

Java/C++ Notes:

  • In Java, use interfaces for the mediator and abstract classes for colleagues
  • C++ would use pure virtual functions and smart pointers for memory management
  • Both languages benefit from explicit interface definitions

Try it yourself: Add a priority system where emergency aircraft get immediate clearance, bumping others in the queue.


Example 3: GUI Form Validation

A practical example showing form fields coordinating through a mediator.

from abc import ABC, abstractmethod
from typing import Dict, Optional

# Mediator interface
class FormMediator(ABC):
    @abstractmethod
    def notify(self, sender: 'FormField', event: str) -> None:
        pass

# Concrete Mediator
class RegistrationForm(FormMediator):
    def __init__(self):
        self.username: Optional['TextField'] = None
        self.email: Optional['TextField'] = None
        self.password: Optional['TextField'] = None
        self.confirm_password: Optional['TextField'] = None
        self.submit_button: Optional['Button'] = None
    
    def notify(self, sender: 'FormField', event: str) -> None:
        if event == "value_changed":
            self._validate_form()
        elif event == "submit":
            self._submit_form()
    
    def _validate_form(self) -> None:
        # Centralized validation logic
        is_valid = True
        errors = []
        
        if self.username and len(self.username.value) < 3:
            is_valid = False
            errors.append("Username must be at least 3 characters")
        
        if self.email and '@' not in self.email.value:
            is_valid = False
            errors.append("Invalid email format")
        
        if self.password and len(self.password.value) < 8:
            is_valid = False
            errors.append("Password must be at least 8 characters")
        
        if (self.password and self.confirm_password and 
            self.password.value != self.confirm_password.value):
            is_valid = False
            errors.append("Passwords don't match")
        
        # Update submit button state
        if self.submit_button:
            self.submit_button.set_enabled(is_valid)
        
        if errors:
            print(f"Validation errors: {', '.join(errors)}")
        else:
            print("Form is valid")
    
    def _submit_form(self) -> None:
        print(f"\n=== Submitting Registration ===")
        print(f"Username: {self.username.value}")
        print(f"Email: {self.email.value}")
        print(f"Registration successful!")

# Colleague base class
class FormField(ABC):
    def __init__(self, name: str, mediator: FormMediator):
        self.name = name
        self._mediator = mediator
        self.value = ""

# Concrete Colleagues
class TextField(FormField):
    def set_value(self, value: str) -> None:
        self.value = value
        print(f"{self.name} changed to: {value}")
        self._mediator.notify(self, "value_changed")

class Button(FormField):
    def __init__(self, name: str, mediator: FormMediator):
        super().__init__(name, mediator)
        self.enabled = False
    
    def set_enabled(self, enabled: bool) -> None:
        self.enabled = enabled
        status = "enabled" if enabled else "disabled"
        print(f"{self.name} is now {status}")
    
    def click(self) -> None:
        if self.enabled:
            print(f"{self.name} clicked")
            self._mediator.notify(self, "submit")
        else:
            print(f"{self.name} is disabled, cannot submit")

# Usage
form = RegistrationForm()

# Create and register fields
form.username = TextField("Username", form)
form.email = TextField("Email", form)
form.password = TextField("Password", form)
form.confirm_password = TextField("Confirm Password", form)
form.submit_button = Button("Submit", form)

print("=== User fills out form ===")
form.username.set_value("jo")  # Too short
form.email.set_value("john.example.com")  # Invalid
form.password.set_value("pass")  # Too short

print("\n=== User corrects input ===")
form.username.set_value("john_doe")
form.email.set_value("john@example.com")
form.password.set_value("SecurePass123")
form.confirm_password.set_value("SecurePass123")

print("\n=== User submits ===")
form.submit_button.click()

Expected Output:

=== User fills out form ===
Username changed to: jo
Validation errors: Username must be at least 3 characters, Invalid email format, Password must be at least 8 characters
Submit is now disabled
Email changed to: john.example.com
Validation errors: Username must be at least 3 characters, Invalid email format, Password must be at least 8 characters
Submit is now disabled
Password changed to: pass
Validation errors: Username must be at least 3 characters, Invalid email format, Password must be at least 8 characters
Submit is now disabled

=== User corrects input ===
Username changed to: john_doe
Validation errors: Invalid email format, Password must be at least 8 characters
Submit is now disabled
Email changed to: john@example.com
Validation errors: Password must be at least 8 characters
Submit is now disabled
Password changed to: SecurePass123
Validation errors: Passwords don't match
Submit is now disabled
Confirm Password changed to: SecurePass123
Form is valid
Submit is now enabled

=== User submits ===
Submit clicked

=== Submitting Registration ===
Username: john_doe
Email: john@example.com
Registration successful!

Key Points:

  • Form fields don’t validate each other directly
  • Mediator contains all cross-field validation logic
  • Button state automatically updates based on form validity
  • Easy to add new validation rules without changing field classes

Try it yourself: Add a “Show Password” checkbox that, when checked, reveals the password field content.

Common Mistakes

1. Making the Mediator Too Complex (God Object)

The Mistake: Putting too much logic in the mediator, turning it into a monolithic “god object” that knows everything and does everything.

# BAD: Mediator doing too much
class ChatRoom:
    def send_message(self, msg, sender):
        # Message validation
        if len(msg) > 500:
            return False
        # Profanity filtering
        msg = self._filter_profanity(msg)
        # Spam detection
        if self._is_spam(sender, msg):
            return False
        # User permission checking
        if not self._has_permission(sender):
            return False
        # Logging
        self._log_message(msg)
        # Analytics
        self._track_analytics(sender, msg)
        # Notification
        self._send_push_notifications(msg)
        # Distribution
        for user in self._users:
            user.receive(msg)

Why it’s wrong: The mediator becomes a maintenance nightmare. Every new feature requires modifying it.

Fix: Use additional patterns (Strategy, Chain of Responsibility) to delegate responsibilities:

# GOOD: Mediator delegates to specialized components
class ChatRoom:
    def __init__(self):
        self._validator = MessageValidator()
        self._filter = ProfanityFilter()
        self._spam_detector = SpamDetector()
        self._logger = MessageLogger()
    
    def send_message(self, msg, sender):
        if not self._validator.validate(msg):
            return False
        msg = self._filter.clean(msg)
        if self._spam_detector.is_spam(sender, msg):
            return False
        self._logger.log(msg)
        self._distribute(msg, sender)

2. Colleagues Communicating Directly

The Mistake: Allowing colleagues to hold references to each other, defeating the purpose of the mediator.

# BAD: User has direct reference to another user
class User:
    def __init__(self, name, mediator):
        self.name = name
        self._mediator = mediator
        self.friends = []  # Direct references!
    
    def send_private_message(self, friend, msg):
        friend.receive(msg)  # Bypasses mediator!

Why it’s wrong: Creates the tight coupling the pattern is meant to eliminate.

Fix: All communication must go through the mediator:

# GOOD: All communication through mediator
class User:
    def __init__(self, name, mediator):
        self.name = name
        self._mediator = mediator
    
    def send_private_message(self, recipient_name, msg):
        self._mediator.send_private_message(msg, self, recipient_name)

3. Not Defining a Mediator Interface

The Mistake: Skipping the abstract mediator interface and going straight to concrete implementation.

# BAD: No interface, hard to test or swap implementations
class ChatRoom:
    def send_message(self, msg, sender):
        # Implementation
        pass

class User:
    def __init__(self, name, chat_room: ChatRoom):  # Coupled to concrete class
        self._chat_room = chat_room

Why it’s wrong: Makes testing difficult and violates Dependency Inversion Principle.

Fix: Always define an abstract mediator:

# GOOD: Interface allows flexibility
class ChatMediator(ABC):
    @abstractmethod
    def send_message(self, msg, sender):
        pass

class User:
    def __init__(self, name, mediator: ChatMediator):  # Depends on abstraction
        self._mediator = mediator

4. Forgetting to Unregister Colleagues

The Mistake: Not providing a way to remove colleagues from the mediator, causing memory leaks.

# BAD: No way to remove users
class ChatRoom:
    def __init__(self):
        self._users = []
    
    def add_user(self, user):
        self._users.append(user)
    # No remove_user method!

Why it’s wrong: Users who leave still consume memory and receive messages.

Fix: Always provide registration and unregistration:

# GOOD: Proper lifecycle management
class ChatRoom:
    def __init__(self):
        self._users = []
    
    def add_user(self, user):
        self._users.append(user)
    
    def remove_user(self, user):
        if user in self._users:
            self._users.remove(user)
            print(f"{user.name} left the chat")

5. Using Mediator When Simple Direct Communication Suffices

The Mistake: Applying the pattern when you only have 2-3 objects with simple interactions.

# BAD: Overkill for simple case
class LoginMediator:
    def __init__(self):
        self.username_field = None
        self.password_field = None
    
    def notify(self, sender, event):
        # Complex coordination for just two fields
        pass

Why it’s wrong: Adds unnecessary complexity. The pattern is for complex, many-to-many relationships.

Fix: Use direct communication for simple cases:

# GOOD: Simple direct interaction
class LoginForm:
    def __init__(self):
        self.username = TextField()
        self.password = TextField()
    
    def validate(self):
        return bool(self.username.value and self.password.value)

Rule of thumb: Use Mediator when you have 4+ objects with complex interaction patterns, or when adding new objects would require changing many existing ones.

Interview Tips

What Interviewers Look For

1. Understanding of the Core Problem

Interviewers want to see you identify when tight coupling is a problem. Be ready to explain:

  • “The Mediator Pattern is useful when objects have many-to-many relationships that create maintenance problems”
  • Draw a before/after diagram showing how n objects with direct communication have O(n²) connections, but with a mediator it’s O(n)

2. Ability to Identify Appropriate Use Cases

Common interview question: “When would you use the Mediator Pattern?”

Strong answer structure:

  • “I’d use it when [specific scenario], for example…”
  • Give concrete examples: chat systems, GUI dialogs, air traffic control
  • Mention the trade-off: “It centralizes control but can become a bottleneck”

Weak answer: “When objects need to communicate” (too vague)


Common Interview Scenarios

Scenario 1: Design a Chat Application

This is a classic mediator problem. Show you understand:

  • Users shouldn’t know about each other
  • The chat room mediates all communication
  • Easy to add features (private messages, rooms) without changing User class

What to say: “I’d use a ChatRoom mediator so users don’t need references to each other. This makes it easy to add features like message history or user blocking without modifying the User class.”

Scenario 2: GUI Component Coordination

Example: “Design a form where enabling a checkbox disables certain fields.”

Strong approach:

  • Identify this as a coordination problem
  • Suggest a FormMediator that handles field interactions
  • Explain how this keeps field classes simple and reusable

Code Implementation Tips

1. Start with the Interface

When asked to implement, always begin with the mediator interface:

class Mediator(ABC):
    @abstractmethod
    def notify(self, sender, event):
        pass

This shows you understand abstraction and testability.

2. Show Bidirectional Setup

Demonstrate that colleagues need a reference to the mediator:

class Colleague:
    def __init__(self, mediator: Mediator):
        self._mediator = mediator
        self._mediator.register(self)  # Important!

3. Handle Edge Cases

Mention considerations like:

  • What if a colleague is removed? (unregistration)
  • Thread safety for concurrent access
  • Circular notification prevention

Comparison Questions

“How is Mediator different from Observer?”

Strong answer:

  • “Observer is one-to-many (one subject, many observers)”
  • “Mediator is many-to-many (colleagues communicate through mediator)”
  • “Observer is about notification; Mediator is about coordination”
  • “They can work together: mediator can use observer to notify colleagues”

“How is Mediator different from Facade?”

Strong answer:

  • “Facade simplifies a complex subsystem with a unified interface”
  • “Mediator coordinates communication between colleagues”
  • “Facade is structural; Mediator is behavioral”
  • “In Facade, subsystem classes don’t know about the facade; in Mediator, colleagues know about the mediator”

Red Flags to Avoid

“Mediator is just a middleman class” — Too simplistic, doesn’t show understanding of the coupling problem

“Use it whenever objects communicate” — Shows lack of judgment about when pattern is appropriate

Implementing mediator without an interface — Suggests you don’t understand abstraction

Making the mediator do everything — Shows you don’t recognize the god object anti-pattern


Bonus Points

Mention real-world frameworks: “Spring’s ApplicationContext acts as a mediator for beans”

Discuss trade-offs: “The mediator can become a bottleneck, so sometimes you need to split it into multiple mediators”

Suggest complementary patterns: “I’d combine Mediator with Observer for event notification” or “Use Strategy pattern to handle different coordination rules”

Show testing awareness: “The mediator interface makes it easy to create a mock for testing colleagues in isolation”


Practice Question

“Design a system where multiple sensors (temperature, humidity, pressure) trigger actions on multiple actuators (heater, fan, alarm). How would you structure this?”

Strong answer approach:

  1. Identify this as a many-to-many coordination problem
  2. Propose a ControlSystemMediator
  3. Explain that sensors notify the mediator of readings
  4. Mediator contains logic to coordinate actuator responses
  5. Mention this makes it easy to add new sensors or actuators
  6. Draw a quick diagram showing the structure

This demonstrates you can apply the pattern to novel problems, not just recite textbook examples.

Key Takeaways

  • The Mediator Pattern decouples objects by making them communicate through a central mediator instead of directly referencing each other, reducing dependencies from O(n²) to O(n).

  • Use it for complex coordination: Apply the pattern when you have multiple objects with intricate interaction patterns, not for simple two-object relationships. Classic use cases include chat systems, GUI forms, and air traffic control.

  • Mediator centralizes control logic: All coordination rules live in one place, making the system easier to understand and modify. However, be careful not to create a “god object” — delegate complex responsibilities to specialized components.

  • Colleagues only know the mediator: Objects (colleagues) should never hold direct references to each other. All communication flows through the mediator interface, enabling loose coupling and easier testing.

  • Trade-offs matter: While the mediator simplifies colleague classes and reduces coupling, it can become a bottleneck or single point of failure. Consider splitting into multiple mediators for large systems, and always define an abstract mediator interface for flexibility.