Mediator pattern
The mediator pattern routes all communication between objects through a central coordinator, replacing an O(N-squared) mesh of direct references with N connections to one hub.
Introduction
The Mediator pattern is a behavioral pattern for coordinating several objects through one coordinator. It is useful when the interaction has routing, filtering, or sequencing rules that would otherwise spread across many participants.
TL;DR / mental model
Think of a star topology: participants talk to the mediator, and the mediator decides which participants should react. Use Mediator for coordination; use Observer when a source only needs to broadcast an event.
The Problem It Solves
You are building a chat application. Each User object needs to send messages to every other user in the room. The naive approach gives each user direct references to all the others:
With 5 users you have 20 direct connections. With 50 users, 2,450. Every join or leave updates every other user. Testing one user in isolation requires bringing along all its contacts, and the coupling graph is a complete mesh that gets harder to change as participants are added.
Here is what changes when you apply the Mediator pattern.
Core idea
Route all communication through a central coordinator. Each component knows only the mediator, never the other components. The mediator owns the coordination logic.
Structure
ChatMediator is the abstraction. ChatRoom is the concrete mediator that holds all users and routes messages. Each ChatUser knows only the mediator interface, never other users directly. The N * (N-1) mesh collapses to N connections (each user to the room) plus the room itself.
Visualize this as a before/after: the "before" is a mesh graph with arrows between every pair of nodes, and the "after" is a star with the mediator at the center. The topology makes the reduction in direct coupling easy to see.
Implementation
// The mediator interface. Components depend on this abstraction,
// never on each other. Swappable for testing (mock mediator).
public interface ChatMediator {
void sendMessage(ChatUser sender, String message);
void addUser(ChatUser user);
void removeUser(ChatUser user);
}N connections, not N-squared
With 50 users and no mediator, you need 2,450 direct connections. With a mediator, you need 50 (each user to the room). The mediator absorbs the routing complexity so components stay simple.
Event-based mediator variant
For systems with multiple event types (not just chat messages), the mediator can route by event type. This prevents the mediator interface from growing a new method for every interaction:
// Generic event that any component can publish through the mediator.
public record MediatorEvent(String type, Map<String, Object> payload) {}
// Event-based mediator: components register interest in specific event types.
public class EventMediator {
private final Map<String, List<Consumer<MediatorEvent>>> handlers =
new ConcurrentHashMap<>();
public void on(String eventType, Consumer<MediatorEvent> handler) {
handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
.add(handler);
}
// Any component can publish. The mediator routes to interested handlers.
public void publish(MediatorEvent event) {
List<Consumer<MediatorEvent>> list = handlers.get(event.type());
if (list != null) {
for (Consumer<MediatorEvent> handler : list) {
handler.accept(event);
}
}
}
}
This variant sits between a pure Mediator and an Event Bus. It is useful when a mediator interface would otherwise grow a method for every event type.
Private messaging through the mediator
A common extension: the mediator can route to a specific user instead of broadcasting. This is still Mediator because the sender does not reference the recipient directly.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.
Related Articles
The observer pattern decouples event producers from consumers. A subject notifies all registered observers of state changes without knowing who they are or what they do.
Chain of responsibility passes a request along a handler pipeline until one processes it. Decouple senders from receivers with composable, reorderable handler chains.
The command pattern turns requests into objects so you can queue, log, and undo operations. Decouple sender from receiver by encapsulating actions.