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.
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 is impossible because it drags in all its contacts. The class is untestable, and the coupling graph is a complete mesh (a hairball that gets worse with every new participant).
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.
In an interview, draw this as a before/after: the "before" is a mesh graph with arrows between every pair of nodes, the "after" is a star with the mediator at the center. The visual difference is the entire argument for the pattern.
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. I reach for it when the mediator interface would otherwise grow to 10+ methods.
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 NotesFromSDE Premium.