Mediator Pattern Interview Questions | JiQuest

add

#

Mediator Pattern

Java design pattern deep dive

Mediator Pattern in Java: 100 interview questions with professional answers.

Learn how the Mediator pattern replaces a tangled web of many-to-many object references with a single coordinating hub, how to keep that hub from becoming a God Object, how it compares to Observer, Facade, and event buses, and how it scales from a GUI dialog to an orchestrated microservices workflow.

100Scenarios
2Core roles
4Related patterns
Mediatorcentral hub Colleague Aknows Mediator only Colleague Bknows Mediator only Colleague Cknows Mediator only Colleague Dknows Mediator only Every colleague talks only to the Mediator there is no direct edge between A, B, C, or D

What makes a good Mediator answer?

Interviewers want to see that you understand Mediator as a coupling trade, not just "a class that calls other classes": colleagues must reference only the mediator abstraction, the mediator must earn its complexity, and you must be able to say honestly when it has grown too large.

Mediator-only referencesColleagues hold a reference only to the Mediator interface, never to sibling colleague classes.
Centralized interaction logicThe Mediator centralizes coordination logic that would otherwise be scattered as many-to-many references between colleagues.
Watch the God ObjectA Mediator that absorbs unrelated business logic beyond coordination becomes a maintenance liability.
Coupling moves, it doesn't vanishMediator reduces coupling between colleagues at the cost of concentrating complexity in one place.
Many objectsreference each other,tangled many-to-many? Interactions complexor stateful? Interactions aresimple one-way notices? Mediatorcentralize the hub Plain Observer /pub-sub may be enough
ApproachUse whenWatch out for
Hand-written Mediator classA bounded set of colleague types in one process needs explicit, easily-debuggable coordination logic, such as a GUI dialog or a chat room.Growing into a God Object once it absorbs unrelated business rules beyond pure coordination.
Event bus / ApplicationEventPublisherComponents within one application should stay decoupled but you don't want a single class enumerating every interaction explicitly.Losing visibility into which listeners react to which events; harder to trace a call chain than an explicit mediator.
Message broker (Kafka / RabbitMQ)Independent services must coordinate without point-to-point calls, and interactions cross process or network boundaries.The broker itself becomes critical infrastructure; ordering, delivery guarantees, and schema evolution need explicit design.

Topics

Mediator basics Q1GoF roles Q2Chat room example Q3 ATC tower analogy Q4GUI dialog mediator Q5Mediator-only reference Q6 Untangling widgets Q7Interface vs concrete Q8Centralizing logic Q9 God Object risk Q10Splitting mediators Q11Helper delegation Q12 Refactor via SRP Q13Coupling trade-off Q14Mediator vs Observer Q15 Observer inside mediator Q16Mediator+Observer example Q17Mediator vs Facade Q18 Facade wrong choice Q19Mediator vs event bus Q20Spring event publisher Q21 Spring order workflow Q22Kafka/RabbitMQ mediator Q23Three coordination styles Q24 Orchestration vs choreography Q25Saga orchestrator Q26Orchestration deep dive Q27 Testing with mocks Q28JUnit chat room test Q29Thread safety Q30 Concurrent registry Q31UI widget worked example Q32Microservice worked example Q33 Bypass mediator bug Q34SPOF/bottleneck risk Q35Circular call bug Q36 Interface granularity Q37Broad vs specific methods Q38Enum event style Q39 Typed methods style Q40Adding new colleague type Q41Interface segregation Q42 Generic mediator base Q43Board game turns Q44Workflow engine as mediator Q45 Mediator + Command Q46Undo/redo mediator Q47Swing/JavaFX controllers Q48 Mediator vs interceptor chain Q49Legacy migration strategy Q50O(n^2) to O(n) Q51 Five-widget explosion Q52Performance overhead Q53Logging interactions Q54 Security considerations Q55Documenting the protocol Q56DI wiring Q57 Constructor vs registration Q58Event sourcing Q59Elevator dispatcher Q60 ATC tower implementation Q61Chat typing indicators Q62Private messages Q63 Ordering guarantees Q64Slow colleague handling Q65Timeout/circuit breaker Q66 Testing exception handling Q67Code review red flags Q68Constructor bloat Q69 Hub-and-spoke architecture Q70Point-to-point vs hub Q71ESB history Q72 Reactive vs polling mediator Q73Joint validation rules Q74Submit-button validation Q75 Mediator vs listener wiring Q76Colleague test isolation Q77Mediator + Strategy Q78 Support ticket routing Q79Ride-share dispatch Q80Versioning the interface Q81 Stateless vs stateful Q82Stateful approval workflow Q83Common first-timer mistakes Q84 Enforcing encapsulation Q85Coordinating state machines Q86Mediator vs shared context Q87 Metrics for capacity planning Q88Multiplayer game server Q89Plugin system mediator Q90 PluginManager example Q91Domain-level vs cross-service Q92Registry memory leaks Q93 Weak references Q94Splitting a God Mediator Q95Incremental introduction Q96 PR review questions Q97Explaining loose coupling Q98Mediator vs API gateway Q99 Capstone: choosing the pattern Q100

Interview questions and answers

Each answer gives the implementation direction, the trade-off to mention, and the production concern that makes the answer stronger.

1. Explain the Mediator design pattern in Java and describe the many-to-many coupling problem it solves between a set of interacting objects.

The Mediator pattern defines an object that encapsulates how a set of other objects, called colleagues, interact with each other. Instead of every colleague holding direct references to every other colleague it needs to talk to, each colleague holds a single reference to the mediator, and the mediator is responsible for routing and coordinating the interaction.

Without Mediator, N colleague classes that all need to coordinate can require up to N times (N-1) direct associations, and every new colleague type means touching every existing colleague that must react to it. With Mediator, adding a colleague means writing one new class plus one change inside the mediator; existing colleagues are untouched.

interface Mediator {
    void notify(Object sender, String event);
}

abstract class Colleague {
    protected final Mediator mediator;
    protected Colleague(Mediator mediator) { this.mediator = mediator; }
}
Behavioral patternMany-to-many to one-to-manyCentralized coordination

2. Describe the classic GoF roles in the Mediator pattern, Mediator interface, ConcreteMediator, Colleague, and ConcreteColleague, and how they map onto a Java implementation.

The Mediator interface declares the coordination methods colleagues call into, such as notify(sender, event). The ConcreteMediator implements that interface, holds references to all the colleagues it coordinates, and contains the actual routing and business rules for how one colleague's action should affect the others.

Colleague is typically an abstract base class or interface holding a reference to the mediator; ConcreteColleague classes are the participants, each exposing behavior the mediator can invoke and each calling back into the mediator whenever something happens that other colleagues might care about.

class ConcreteMediator implements Mediator {
    private final ColleagueA a;
    private final ColleagueB b;

    ConcreteMediator(ColleagueA a, ColleagueB b) { this.a = a; this.b = b; }

    @Override
    public void notify(Object sender, String event) {
        if (sender == a && "changed".equals(event)) {
            b.reactToA();
        }
    }
}

3. Walk through a chat-room example in Java where a ChatRoom mediator coordinates multiple User colleague objects that never reference each other directly.

Each User holds a reference to the shared ChatRoom mediator and calls chatRoom.send(this, message) instead of iterating over other users itself. The ChatRoom mediator maintains the list of registered users and is the only object that knows how to fan a message out to everyone except the sender.

interface ChatMediator {
    void register(User user);
    void send(User sender, String message);
}

class ChatRoom implements ChatMediator {
    private final List<User> users = new ArrayList<>();

    @Override public void register(User user) { users.add(user); }

    @Override
    public void send(User sender, String message) {
        for (User user : users) {
            if (user != sender) user.receive(sender.getName(), message);
        }
    }
}

class User {
    private final String name;
    private final ChatMediator mediator;

    User(String name, ChatMediator mediator) {
        this.name = name; this.mediator = mediator;
        mediator.register(this);
    }

    String getName() { return name; }
    void send(String message) { mediator.send(this, message); }
    void receive(String from, String message) { System.out.println(name + " got from " + from + ": " + message); }
}

No User instance ever stores a reference to another User; the ChatRoom is the only place that knows the full set of participants.

4. Use the air-traffic-control-tower analogy to explain the Mediator pattern, then implement it in Java with Aircraft colleagues and a ControlTower mediator.

Aircraft approaching an airport do not negotiate landing order directly with each other over the radio; each aircraft talks only to the control tower, and the tower decides who lands next based on the full picture it holds. This is exactly the Mediator shape: many peers, one coordinator, no peer-to-peer channel.

interface ControlTower {
    boolean requestLanding(Aircraft aircraft);
    void notifyLanded(Aircraft aircraft);
}

class Airport implements ControlTower {
    private Aircraft runwayOccupant;

    @Override
    public synchronized boolean requestLanding(Aircraft aircraft) {
        if (runwayOccupant != null) return false;
        runwayOccupant = aircraft;
        return true;
    }

    @Override
    public synchronized void notifyLanded(Aircraft aircraft) {
        if (runwayOccupant == aircraft) runwayOccupant = null;
    }
}
Real-world analogySingle coordinating authority

5. Design a GUI dialog mediator in Java where form fields, a checkbox, a textbox, and a submit button, enable and disable each other only through a single dialog mediator.

Each widget calls into the dialog mediator whenever its own state changes; the mediator decides what that means for the other widgets, and no widget ever calls a method on another widget directly.

interface DialogMediator {
    void widgetChanged(String widgetId);
}

class RegistrationDialog implements DialogMediator {
    private final Checkbox termsCheckbox = new Checkbox(this);
    private final Textbox emailBox = new Textbox(this);
    private final Button submitButton = new Button();

    @Override
    public void widgetChanged(String widgetId) {
        boolean canSubmit = termsCheckbox.isChecked() && emailBox.getText().contains("@");
        submitButton.setEnabled(canSubmit);
    }
}

class Checkbox {
    private final DialogMediator mediator;
    private boolean checked;
    Checkbox(DialogMediator mediator) { this.mediator = mediator; }
    void toggle() { checked = !checked; mediator.widgetChanged("terms"); }
    boolean isChecked() { return checked; }
}

Checkbox and Textbox never know that a Button exists; only the RegistrationDialog mediator connects the three.

6. Explain why Colleague classes in the Mediator pattern should hold a reference only to the Mediator interface type, never to concrete sibling colleagues.

If a colleague held a direct field referencing another concrete colleague class, you would be right back to the many-to-many coupling Mediator exists to remove, just relabeled. Depending only on the Mediator interface means a colleague can be unit tested with a mock mediator, swapped between different concrete mediators, and reused across applications that wire colleagues together differently.

// Wrong: colleague coupled to a concrete sibling
class Checkbox {
    private final Button submitButton; // tight coupling reintroduced
}

// Right: colleague coupled only to the mediator abstraction
class Checkbox {
    private final DialogMediator mediator;
}
Dependency inversionTestability

7. Show how introducing a Mediator eliminates a tangled web of direct references between UI widgets that previously all referenced each other.

Before Mediator, a form with five interdependent widgets can require each widget to hold references to several others, so a change to one widget's logic risks a ripple of edits across every widget that reacts to it. After Mediator, every widget holds exactly one reference, to the mediator, and all cross-widget rules live in one place.

// Before: widgets reference each other directly
class Checkbox { Button submitButton; Textbox emailBox; List list; }
class Textbox  { Button submitButton; Checkbox termsCheckbox; }
class ListBox  { Button submitButton; }

// After: every widget references only the mediator
class Checkbox { DialogMediator mediator; }
class Textbox  { DialogMediator mediator; }
class ListBox  { DialogMediator mediator; }

The total number of associations drops from a dense mesh to a simple star, and the coordination logic that used to be smeared across five classes now lives in one reviewable place.

8. Explain the difference between the Mediator interface and a ConcreteMediator class, and why programming colleagues against the interface matters for testability.

The Mediator interface is the narrow contract colleagues depend on; the ConcreteMediator is one particular implementation containing the real coordination rules, colleague references, and any internal state. Colleagues should only ever import the interface type, never the concrete class.

This separation lets a unit test supply a trivial fake or Mockito mock implementing Mediator when testing a single colleague in isolation, without constructing the entire real dialog, chat room, or workflow the concrete mediator represents.

9. Discuss how a Mediator centralizes interaction logic that would otherwise be duplicated across many colleague classes.

Without a mediator, a rule like "the submit button is enabled only when the terms checkbox is checked and the email field is valid" has to be implemented somewhere reachable by both the checkbox's change handler and the textbox's change handler, often leading to the same conditional logic copied in two places or a fragile listener chain.

With a mediator, that rule is written exactly once, inside the mediator's reaction to either widget's change notification, and both widgets simply announce "I changed" without knowing or caring what the consequence is.

DRY coordination logicSingle source of truth

10. What is the risk of a Mediator becoming a "God Object", and what symptoms indicate that a mediator class has absorbed too much unrelated business logic?

Because the Mediator is the one place that "knows everything," it is tempting to keep adding logic to it rather than to the colleagues, and over time it can grow into a God Object: one enormous class doing validation, persistence, notification, and business rules for an entire subsystem, far beyond pure coordination.

Symptoms include a mediator class with dozens of unrelated methods, a constructor with a long list of colleague dependencies that keeps growing, methods that reach deep into a colleague's internal state rather than calling its public behavior, and the mediator becoming the single class every developer on the team is afraid to touch.

Warning sign If the mediator imports domain services, repositories, and validation rules unrelated to routing calls between its colleagues, that logic likely belongs elsewhere.

11. Describe concrete strategies for splitting an overgrown Mediator into multiple smaller, focused mediators.

Group colleagues and interactions by cohesive responsibility, for example separate a dialog's "field validation" mediator from its "navigation between wizard steps" mediator, even though both coordinate widgets on the same screen. Each smaller mediator implements its own narrow interface and only the colleagues relevant to that concern register with it.

interface ValidationMediator { void fieldChanged(String fieldId); }
interface WizardNavigationMediator { void stepCompleted(int stepIndex); }

// One dialog can compose both, rather than one mediator doing everything
class RegistrationWizard {
    private final ValidationMediator validation = new FormValidationMediator();
    private final WizardNavigationMediator navigation = new StepNavigationMediator();
}

If two concerns genuinely need to talk to each other, let one smaller mediator hold a reference to the other's interface rather than merging both back into one class.

12. Explain how delegating sub-logic to helper or strategy classes keeps a Mediator's core responsibility, coordination, separate from domain rules.

Instead of writing validation, pricing, or eligibility rules inline inside the mediator's reaction methods, the mediator calls out to a dedicated helper or strategy object that owns that rule, and only uses the result to decide which colleague to notify next. The mediator stays a thin router; the rule itself is independently testable and reusable outside the mediator entirely.

class RegistrationDialog implements DialogMediator {
    private final EligibilityRule eligibilityRule; // delegated helper, not inline logic

    @Override
    public void widgetChanged(String widgetId) {
        boolean eligible = eligibilityRule.isEligible(termsCheckbox.isChecked(), emailBox.getText());
        submitButton.setEnabled(eligible);
    }
}
Single Responsibility PrincipleThin mediator, rich helpers

13. How would you refactor a 2000-line ConcreteMediator class using the Single Responsibility Principle?

First, catalogue every method and group them by which colleagues and which business concern they touch; a mediator that large usually reveals two or three unrelated coordination concerns hiding inside one class. Extract each concern into its own smaller mediator interface and implementation, moving only the colleagues relevant to that concern to register with the new, narrower mediator.

Next, pull out any logic that is not actually about routing between colleagues, validation rules, formatting, persistence calls, into standalone helper classes the mediator delegates to. Finally, add characterization tests around the original class's behavior before starting the split, so the refactor can be verified against the exact previous behavior rather than a guess at what it "should" do.

14. Explain the trade-off the Mediator pattern makes: reduced coupling between colleagues versus concentrated complexity in one place.

Mediator does not remove complexity from a system, it relocates it. Before Mediator, complexity is distributed as many small pairwise dependencies scattered across colleague classes; after Mediator, that same complexity is concentrated into one class's coordination logic. The total complexity of the interactions themselves does not shrink just because one class now holds it.

The trade is worthwhile because concentrated complexity in one reviewable, testable class is generally easier to reason about, and easier to change safely, than the same complexity smeared across every pair of colleague classes, provided the mediator itself is kept from growing unchecked into a God Object.

Coupling vs cohesion trade-off

15. Compare the Mediator and Observer patterns: how does a one-directional broadcast from a subject to many observers differ from a Mediator hub that both receives from and directs multiple colleagues?

Observer models a one-to-many, one-directional relationship: a single subject changes state and broadcasts that change to any number of registered observers, none of whom talk back to the subject as part of the pattern, and there is no central object deciding how observers should react to each other. Mediator models a hub with bidirectional traffic: colleagues send interactions in, and the mediator can direct instructions back out to any other colleague, actively coordinating behavior rather than just broadcasting a fact.

Put differently, Observer answers "who should be told that something happened," while Mediator answers "given that something happened, what should happen next, and to whom." A Mediator often needs Observer-like notification internally as its input mechanism, but it adds a coordinating decision layer Observer alone does not have.

One-way vs hubNotification vs coordination

16. Explain how a ConcreteMediator is often implemented internally using the Observer pattern, with colleagues registering listeners on the mediator.

A common implementation style has each colleague expose a listener interface and the mediator register itself as an observer of every colleague, or alternatively colleagues call an explicit mediator.notify(this, event) method that plays the same role as an observer callback. Either way, Observer supplies the plumbing for "tell the mediator something happened"; Mediator supplies the decision logic for "now do something about it."

class ConcreteMediator implements Mediator {
    // colleague-facing method plays the same role as Observer's update()
    @Override
    public void notify(Object sender, String event) {
        // observer-style callback in, coordination logic out
    }
}

17. Give a worked Java example combining Mediator and Observer where colleagues fire events that the mediator observes and reacts to by directing other colleagues.

Each colleague exposes a small listener interface; the mediator implements that listener for every colleague it manages, so colleague code only ever fires an event without knowing who is listening or what happens next.

interface FieldChangeListener { void onChanged(Field source); }

class Field {
    private final List<FieldChangeListener> listeners = new ArrayList<>();
    void addListener(FieldChangeListener l) { listeners.add(l); }
    void setValue(String v) {
        this.value = v;
        for (FieldChangeListener l : listeners) l.onChanged(this); // Observer-style fan-out
    }
}

class FormMediator implements FieldChangeListener {
    private final Field email, terms;
    private final Button submit;

    FormMediator(Field email, Field terms, Button submit) {
        this.email = email; this.terms = terms; this.submit = submit;
        email.addListener(this); terms.addListener(this); // mediator subscribes, Observer-style
    }

    @Override
    public void onChanged(Field source) {
        submit.setEnabled(email.isValid() && terms.isChecked()); // Mediator-style decision
    }
}

18. Compare the Mediator and Facade patterns: why does Facade's one-directional simplification of subsystem calls differ from Mediator's bidirectional coordination between peer objects?

Facade provides a single, simplified entry point in front of a complex subsystem so a client can make one call instead of many; the flow is one-directional, client into facade into subsystem, and the subsystem's internal classes are not being coordinated with each other by the facade, they simply get called in sequence. Facade does not manage ongoing bidirectional communication between subsystem components themselves.

Mediator, by contrast, exists specifically to manage bidirectional communication between peer objects that are equals, none of them is "the client" and none is "the subsystem"; each colleague can both trigger interactions and be the target of instructions coming back from the mediator. Facade simplifies access; Mediator coordinates peers.

One-directional vs bidirectionalSubsystem access vs peer coordination

19. Give an example where Facade would be the wrong choice and Mediator is needed because two subsystem components need bidirectional coordination.

Suppose an OrderFacade.placeOrder() simply calls inventory, then payment, then shipping in sequence, a legitimate one-directional Facade. Now suppose inventory needs to tell payment "the price changed because we substituted a backordered item," and payment needs to tell inventory "the payment failed, release the reservation," a genuine back-and-forth between two peer subsystems. A facade method calling three services in a row cannot express that; you need a mediator that both inventory and payment call into and receive instructions from.

Rule of thumb If component A needs to react to component B's outcome and vice versa, reach for Mediator; if the flow is strictly client-to-subsystem, Facade is enough.

20. Compare the Mediator pattern with an event bus or publish-subscribe architecture at distributed-systems scale: what changes when colleagues live in separate services rather than a single process?

In-process, a Mediator is a single object with direct method references to its colleagues, coordination is synchronous, and failures are ordinary exceptions the mediator can catch. At distributed scale, colleagues are separate services that cannot hold in-memory references to each other or to a shared mediator object; an event bus or message broker plays the mediator's routing role instead, with events serialized, transmitted over the network, and delivered asynchronously.

The core idea, "peers coordinate through a hub instead of calling each other directly," survives the transition, but the hub must now also handle network partitions, message ordering, at-least-once delivery, and retries, concerns a single-process Mediator never has to think about.

In-process vs distributedSynchronous vs asynchronous coordination

21. Explain how Spring's ApplicationEventPublisher and @EventListener act as a lightweight, in-process Mediator between application components.

A Spring component publishes a domain event through ApplicationEventPublisher.publishEvent(event) without knowing, or needing to know, which other beans will react to it. The Spring ApplicationContext plays the mediator's role: it holds the registry of listeners and routes each published event to every method annotated @EventListener whose parameter type matches.

@Component
class OrderService {
    private final ApplicationEventPublisher publisher;

    void placeOrder(Order order) {
        // OrderService never calls InventoryService or NotificationService directly
        publisher.publishEvent(new OrderCreatedEvent(order));
    }
}

@Component
class InventoryListener {
    @EventListener
    void onOrderCreated(OrderCreatedEvent event) {
        // reacts without OrderService knowing this class exists
    }
}
Spring eventsIn-process mediator

22. Walk through implementing an order-processing workflow in Spring where OrderCreatedEvent and InventoryReservedEvent are coordinated by the Spring event mechanism instead of direct service-to-service calls.

OrderService publishes OrderCreatedEvent; InventoryService listens for it, attempts a reservation, and publishes either InventoryReservedEvent or InventoryRejectedEvent; PaymentService listens for the reserved event and attempts a charge. No service holds a direct reference to another.

@Component
class InventoryService {
    private final ApplicationEventPublisher publisher;

    @EventListener
    void onOrderCreated(OrderCreatedEvent event) {
        boolean reserved = tryReserve(event.orderId());
        publisher.publishEvent(reserved
            ? new InventoryReservedEvent(event.orderId())
            : new InventoryRejectedEvent(event.orderId()));
    }
}

@Component
class PaymentService {
    @EventListener
    void onInventoryReserved(InventoryReservedEvent event) {
        charge(event.orderId());
    }
}
Watch out Default @EventListener methods run synchronously on the publishing thread and inside the same transaction; use @TransactionalEventListener or @Async deliberately when that is not what you want.

23. Explain how a Kafka topic or RabbitMQ exchange can act as a mediator-like intermediary between microservices that would otherwise call each other point-to-point.

Instead of the order service calling the inventory service's REST endpoint directly, and the inventory service calling the shipping service directly, each service publishes domain events to a shared Kafka topic or RabbitMQ exchange, and consumes only the events relevant to it. The broker becomes the single place that knows how events flow between services, exactly the role a Mediator object plays inside one process, just realized as infrastructure instead of a Java class.

This removes the need for each service to know the network address or API contract of every other service it affects; a service that only needs to react to "order created" subscribes to that event type and never needs to be told about a new downstream consumer being added later.

Architecture-level MediatorDecoupled services

24. Compare a hand-written Java Mediator class, an in-process event bus such as ApplicationEventPublisher, and a message broker such as Kafka or RabbitMQ as three different granularities of the same coordinating idea.

All three exist to let peers coordinate without calling each other directly, but they differ in scope and cost. A hand-written mediator class is the cheapest and most explicit, ideal for a bounded set of colleagues inside one class or module where you want the interaction logic to be readable in one place. An in-process event bus scales that idea to an entire application with many beans, trading some readability of "who reacts to what" for much lower coupling between components that may not even know of each other's existence at compile time.

A message broker scales the same idea again, across process and network boundaries, adding durability, retries, and asynchronous delivery, at the cost of operational complexity and weaker guarantees about ordering and timing. See the comparison table above the topics list for a fuller breakdown of when to reach for each.

25. Explain the orchestrator pattern in microservices, such as a saga orchestrator, as an architecture-level Mediator, versus choreography where services react to each other's events without a central coordinator.

An orchestrator is a dedicated service or component that explicitly calls each participant in a multi-step distributed workflow, in a chosen order, and reacts to each participant's success or failure by deciding what to call next, including compensating actions. This is architecturally identical to the Mediator pattern: participants (colleagues) do not call each other, they respond to the orchestrator (the mediator), which holds the entire interaction logic.

Choreography is the opposite extreme: each service reacts to events published by others with no central coordinator at all, closer to a pure event bus with no single object holding the workflow's overall logic. Choreography avoids a central point of control but makes the end-to-end workflow harder to see in one place, since it is scattered across every participating service's event handlers.

Orchestration = MediatorChoreography = decentralized events

26. Design a saga orchestrator in Java for a distributed order, payment, and inventory workflow, showing how the orchestrator plays the Mediator role across service calls.

The orchestrator holds references only to each participant's client interface, never do the participants reference each other, and it drives the sequence: reserve inventory, charge payment, confirm shipping, issuing compensating calls if a later step fails.

class OrderSagaOrchestrator {
    private final InventoryClient inventory;
    private final PaymentClient payment;
    private final ShippingClient shipping;

    void execute(OrderRequest request) {
        ReservationResult reservation = inventory.reserve(request);
        if (!reservation.success()) { fail(request, "inventory"); return; }

        PaymentResult charge = payment.charge(request);
        if (!charge.success()) {
            inventory.release(reservation.id()); // compensating action
            fail(request, "payment");
            return;
        }

        shipping.schedule(request);
    }
}
Saga orchestrationCompensating transactions

27. Explain the difference between orchestration and choreography in distributed sagas in more depth, mapping orchestration explicitly to the Mediator pattern and discussing when each style is preferable.

Orchestration centralizes the saga's control flow into one component that every participant answers to, giving you a single place to read the entire business process, easy centralized error handling and compensation logic, and straightforward testing since the orchestrator's logic can be unit tested with mocked participant clients, exactly like testing a Mediator with mocked colleagues.

Choreography avoids that central component entirely, which reduces any single service's blast radius and avoids a potential bottleneck, but the overall business process becomes implicit, reconstructed only by reading every participating service's event handlers together. Teams typically prefer orchestration for complex, multi-step, business-critical workflows where visibility and compensation matter, and choreography for simpler, more independent reactions where no single service needs to "own" the whole flow.

28. How would you unit test a Mediator by mocking its colleague interfaces and asserting the mediator invokes the correct colleague methods in the correct order?

Because a mediator's entire job is coordination, tests should construct the mediator with mocked colleague dependencies, trigger an interaction through the mediator's public entry point, and verify the correct colleague methods were called with the correct arguments and in the expected sequence.

@Test
void releasesInventoryWhenPaymentFails() {
    InventoryClient inventory = mock(InventoryClient.class);
    PaymentClient payment = mock(PaymentClient.class);
    when(inventory.reserve(any())).thenReturn(ReservationResult.success("res-1"));
    when(payment.charge(any())).thenReturn(PaymentResult.failure());

    OrderSagaOrchestrator orchestrator = new OrderSagaOrchestrator(inventory, payment, mock(ShippingClient.class));
    orchestrator.execute(new OrderRequest("order-1"));

    verify(inventory).release("res-1"); // compensating call happened
    verifyNoInteractions(payment.getClass()); // illustrative; use verify ordering in practice
}

29. Write a JUnit and Mockito test verifying that a ChatRoom mediator delivers a message from one User colleague to all other registered Users but not back to the sender.

Register several mock or fake User instances with the ChatRoom, send a message from one of them, and assert that every other user's receive method was called while the sender's was not.

@Test
void doesNotEchoMessageBackToSender() {
    ChatRoom chatRoom = new ChatRoom();
    User alice = mock(User.class);
    User bob = mock(User.class);
    when(alice.getName()).thenReturn("alice");
    chatRoom.register(alice);
    chatRoom.register(bob);

    chatRoom.send(alice, "hello");

    verify(bob).receive("alice", "hello");
    verify(alice, never()).receive(anyString(), anyString());
}

30. Discuss thread-safety concerns for a shared Mediator instance handling concurrent interactions from multiple colleague threads.

If a mediator is shared by colleagues running on different threads, such as a chat room mediator serving concurrent client connections, its internal registry of colleagues and any coordination state must be protected from concurrent modification, otherwise registering a new colleague while another thread is iterating to broadcast a message can throw a ConcurrentModificationException or silently drop a recipient.

The mediator's coordination logic itself, deciding what to do in response to an event, should also be examined for race conditions if two colleagues can trigger conflicting decisions at nearly the same time, such as two aircraft both requesting the same runway.

Common bug Iterating a plain ArrayList of colleagues while another thread registers a new one is a classic source of intermittent ConcurrentModificationException failures in production chat and pub-sub mediators.

31. How would you make a ConcreteMediator's internal colleague registry thread-safe using java.util.concurrent collections?

Replace a plain ArrayList with a CopyOnWriteArrayList when registrations are rare relative to broadcasts, since reads (iterating to notify colleagues) vastly outnumber writes (registering or unregistering a colleague), and iteration never throws even if another thread mutates the list concurrently.

class ChatRoom implements ChatMediator {
    private final List<User> users = new CopyOnWriteArrayList<>();

    @Override public void register(User user) { users.add(user); }

    @Override
    public void send(User sender, String message) {
        for (User user : users) { // safe even if another thread registers concurrently
            if (user != sender) user.receive(sender.getName(), message);
        }
    }
}

For coordination state beyond the registry itself, such as the air-traffic-control tower's runway occupant, use explicit synchronization or an AtomicReference so the decision logic itself is not subject to a race.

32. Walk through a worked example of a Mediator coordinating multiple UI widgets, a textbox, a checkbox, a list, and a submit button, in a registration form.

Each widget notifies the mediator of its own change; the mediator inspects the combined state of all widgets it cares about and updates the others accordingly, here disabling the submit button until every required field is valid and populating a dependent list only once a category checkbox is selected.

class RegistrationFormMediator implements DialogMediator {
    private final Textbox email; private final Checkbox terms; private final ListBox categories; private final Button submit;

    @Override
    public void widgetChanged(String widgetId) {
        if ("category".equals(widgetId)) {
            categories.setEnabled(!categories.getSelection().isEmpty());
        }
        submit.setEnabled(email.getText().contains("@") && terms.isChecked() && !categories.getSelection().isEmpty());
    }
}

33. Walk through a worked example of a Mediator orchestrating multiple microservice calls, an inventory check, a payment charge, and a shipping schedule, as one coordinated workflow.

This is the same shape as Q26's saga orchestrator, viewed from the coordination-logic angle: the mediator receives one incoming request, calls the first participant, inspects its result, and decides whether to proceed to the next participant, roll back a previous step, or fail the whole workflow, all without any participant client knowing the others exist.

WorkflowResult result = orchestrator.execute(orderRequest);
if (!result.success()) {
    log.warn("Order {} failed at step {}", orderRequest.id(), result.failedStep());
}
Microservice orchestrationCompensating rollback

34. Describe the bug where a colleague bypasses the mediator and calls another colleague directly, reintroducing tight coupling. How would you catch this in code review?

The moment a colleague acquires a reference to another concrete colleague and invokes a method on it directly, the point of the pattern is defeated for that interaction: the two colleagues are now coupled, changes to one may require changes to the other, and the mediator's view of "everything that happens" becomes incomplete because this interaction bypasses it entirely.

class Checkbox {
    private final DialogMediator mediator;
    private Button submitButtonDirectRef; // red flag: colleague holding a sibling reference

    void toggle() {
        submitButtonDirectRef.setEnabled(isChecked()); // bypasses the mediator
    }
}

In code review, flag any colleague class whose fields include another concrete colleague type rather than only the mediator interface, and flag any colleague method that calls a sibling's setter or business method directly instead of going through mediator.notify(...).

Review checklist item Search colleague classes for import statements or field types referencing sibling colleague classes; a legitimate Mediator-based colleague should only import the mediator interface.

35. Explain how the Mediator itself can become a single point of failure or a performance bottleneck, and how to mitigate this in a high-throughput system.

Because every interaction between colleagues funnels through one object, an exception thrown inside the mediator, or a lock held too long around its coordination logic, can stall or break every colleague's interaction at once, not just one pairwise relationship as it would in a direct-reference design. At high throughput, if the mediator does synchronous, blocking work (a database call, a slow downstream service) inside its coordination method, it can become a serialization point that limits overall system throughput.

Mitigations include keeping the mediator's own logic fast and non-blocking, delegating slow work to asynchronous helpers rather than doing it inline, avoiding a single coarse-grained lock across the whole coordination method, and, at distributed scale, replacing a single in-process mediator with a horizontally scalable message broker so no single instance is a hard bottleneck.

36. Describe a circular-call bug between mediator and colleague that causes infinite recursion, and how to guard against it.

If colleague A notifies the mediator, the mediator reacts by calling colleague B, and colleague B's reaction triggers another notification that eventually causes the mediator to call colleague A again in a way that re-triggers colleague A's original notification, the call chain can loop indefinitely until the stack overflows. This is especially easy to introduce when the mediator's reaction to an event calls a setter on a colleague, and that setter itself fires a change notification back to the mediator.

void setValue(String v) {
    this.value = v;
    mediator.notify(this, "changed"); // if the mediator's reaction calls setValue again, this loops
}

Guard against it by having colleague setters check whether the new value actually differs before firing a notification, and by having the mediator track whether it is already mid-dispatch for a given colleague so a re-entrant call is suppressed rather than recursed into.

Re-entrancy bugGuard conditions

37. Discuss designing the Mediator interface's granularity: one broad notify(event) method versus many specific methods like onFieldChanged and onSubmitClicked.

A broad single-method interface, such as void notify(Object sender, String event), is easy to extend without touching the interface itself, since new event types are just new string or enum values, but it pushes type-checking and dispatch logic (often a large if-else or switch) into the mediator's implementation and loses compile-time verification that a colleague sends a valid event name.

A narrow, many-method interface, such as separate onFieldChanged(Field) and onSubmitClicked() methods, gives compile-time safety and clear, self-documenting call sites, but every new kind of interaction requires adding a method to the interface and to every implementation of it, which does not scale gracefully as the number of interaction types grows.

38. What are the trade-offs of a broad single notify(Object event) method on a Mediator interface versus a strongly-typed set of specific callback methods?

The broad style scales well as the number of event kinds grows, since adding a new event type is just a new class or enum constant, not an interface change, but at the cost of runtime type checks, pattern matching, or string comparisons inside the mediator, and the loss of IDE-assisted navigation from a colleague's call site to the exact handling logic.

The narrow style keeps every interaction discoverable and type-safe at compile time, and refactoring tools can find every caller of a specific method reliably, but a mediator interface with thirty specific methods becomes unwieldy to implement and to keep synchronized across colleagues as requirements evolve.

Extensibility vs type safety

39. Show a Java Mediator interface designed with an enum-based event type plus a single handle(Event) method, and discuss its extensibility trade-offs.

An enum or sealed event hierarchy gives some of the type safety of specific methods while keeping the interface itself stable as new event kinds are added, since the mediator's dispatch can use an exhaustive switch that the compiler checks for completeness with a sealed type.

sealed interface DialogEvent permits FieldChanged, SubmitClicked {}
record FieldChanged(String fieldId) implements DialogEvent {}
record SubmitClicked() implements DialogEvent {}

interface DialogMediator {
    void handle(DialogEvent event);
}

class RegistrationDialog implements DialogMediator {
    @Override
    public void handle(DialogEvent event) {
        switch (event) {
            case FieldChanged fc -> onFieldChanged(fc.fieldId());
            case SubmitClicked sc -> onSubmit();
        }
    }
}

Adding a new event still means adding a new record and a new switch arm everywhere the sealed type is switched on, but the compiler enforces that every mediator implementation handles it, catching gaps that a plain string-keyed notify would miss silently.

40. Show a Java Mediator interface designed with several explicit typed methods, one per interaction, and discuss its extensibility trade-offs versus the broad-event style.

Each interaction gets its own named method with a specific signature, maximizing readability at each call site and giving the compiler full visibility into every parameter's type.

interface DialogMediator {
    void onEmailChanged(String newEmail);
    void onTermsToggled(boolean checked);
    void onSubmitClicked();
}

This works well while the set of interactions is small and stable. Once a system needs its tenth or twentieth distinct interaction type, every mediator implementation across the codebase must add the new method, and colleagues that only care about a couple of interactions still see the full interface surface, which is when teams typically migrate toward the sealed-event or broad-notify styles from Q37 to Q39.

41. Explain how adding a new Colleague type to a Mediator-based system affects the Mediator interface and its ConcreteMediator implementation.

With a broad notify(sender, event)-style interface, adding a new colleague type usually requires no interface change at all; the new colleague simply calls the existing method, and the ConcreteMediator gains a new branch or case to react to events from it, plus a new field to hold the reference. With a narrow, many-method interface, adding a colleague whose interactions do not already have dedicated methods requires adding new methods to the interface and to every existing implementation, even ones that do not care about the new colleague.

Either way, the key benefit over a no-mediator design holds: existing colleague classes are never modified just because a new colleague type was introduced, only the mediator (and possibly its interface) changes.

42. Discuss the Interface Segregation Principle as it applies to splitting a bloated Mediator interface into smaller, colleague-specific mediator interfaces.

The Interface Segregation Principle says clients should not be forced to depend on methods they do not use. A single fat Mediator interface with methods for every colleague type in a large system forces a colleague that only cares about two of those methods to still depend on, and potentially be affected by changes to, the other thirty.

// Fat interface: every colleague depends on methods it never calls
interface Mediator { void onFieldChanged(String id); void onOrderPlaced(Order o); void onShipmentUpdated(Shipment s); }

// Segregated: a Checkbox only depends on the slice it actually uses
interface FieldMediator { void onFieldChanged(String id); }
interface OrderMediator { void onOrderPlaced(Order o); }

A single ConcreteMediator class can implement several small interfaces simultaneously, giving each colleague a narrow view while still centralizing the actual coordination logic in one place if that is still desired.

43. How would you use generics to build a reusable, type-safe Mediator base class across different colleague-event pairings?

A generic base mediator can parameterize over the colleague type and event type, letting the dispatch and registration logic be written once and reused across otherwise unrelated mediator implementations, while still giving each concrete usage compile-time type safety for its specific colleague and event types.

abstract class AbstractMediator<C, E> {
    private final List<C> colleagues = new ArrayList<>();

    void register(C colleague) { colleagues.add(colleague); }
    List<C> colleagues() { return List.copyOf(colleagues); }

    abstract void handle(C sender, E event);
}

class ChatMediator extends AbstractMediator<User, String> {
    @Override
    void handle(User sender, String event) { /* chat-specific coordination */ }
}
Generic reuseCompile-time colleague/event typing

44. Implement a Mediator-coordinated multiplayer board game turn sequence where Player colleagues never know whose turn is next except through the mediator.

Each Player calls mediator.endTurn(this) when done; the GameMediator holds the ordered list of players and the current turn index, and is the only object that decides and announces whose turn comes next.

interface GameMediator {
    void endTurn(Player player);
}

class TurnBasedGame implements GameMediator {
    private final List<Player> players;
    private int currentIndex = 0;

    @Override
    public void endTurn(Player player) {
        if (players.get(currentIndex) != player) return; // ignore out-of-turn calls
        currentIndex = (currentIndex + 1) % players.size();
        players.get(currentIndex).notifyYourTurn();
    }
}

No Player instance ever asks another player "is it your turn"; the mediator is the sole authority on turn order.

45. Explain how a workflow or orchestration engine, such as a state machine driving a business process, functions as a Mediator between the steps of the workflow.

A workflow engine holds the definition of a multi-step process and, as each step's task completes, decides which step runs next based on the outcome, exactly the Mediator role applied to process steps instead of UI widgets or microservices. Individual step implementations, tasks, or activities do not call each other; they report completion (or failure) back to the engine, which alone knows the full graph of what should happen next.

This is why workflow engines such as a BPMN engine or a custom state-machine-driven orchestrator are frequently described as "the mediator of the business process," even when the term Mediator is never mentioned explicitly in their documentation.

Process orchestrationState machine as mediator

46. Compare the Mediator pattern with the Command pattern: how can a Mediator use Command objects internally to represent and queue interactions between colleagues?

Command encapsulates a request as an object, with an execute() method and optionally an undo() method, decoupling the invoker of an action from the code that performs it. A Mediator can use Command objects as the internal representation of "what should happen next" once it decides how to react to a colleague's notification, letting it queue, log, delay, or undo interactions rather than executing coordination logic immediately and irreversibly.

interface MediatorCommand { void execute(); void undo(); }

class NotifySubmitEnabledCommand implements MediatorCommand {
    private final Button submit; private final boolean enable;
    NotifySubmitEnabledCommand(Button submit, boolean enable) { this.submit = submit; this.enable = enable; }
    @Override public void execute() { submit.setEnabled(enable); }
    @Override public void undo() { submit.setEnabled(!enable); }
}
Mediator + Command

47. Design a Mediator that supports undo and redo of colleague interactions by combining it with the Command pattern.

Every coordination decision the mediator makes is wrapped as a MediatorCommand and pushed onto an undo stack instead of being applied directly; undoing pops the most recent command and calls its undo(), while redoing re-executes a command popped from a separate redo stack.

class UndoableMediator implements DialogMediator {
    private final Deque<MediatorCommand> undoStack = new ArrayDeque<>();

    void apply(MediatorCommand command) {
        command.execute();
        undoStack.push(command);
    }

    void undo() {
        if (!undoStack.isEmpty()) undoStack.pop().undo();
    }
}

This works cleanly because the mediator is already the single place all cross-colleague decisions pass through; wrapping each decision as a command costs little extra and buys undo/redo for free.

48. Explain how Java Swing or JavaFX controller classes often play the Mediator role in practice, coordinating between UI components defined in FXML or a layout file.

A JavaFX @FXML-annotated controller class typically holds references to every named UI control declared in its FXML file, and its event handler methods, such as a checkbox's onAction, read and update other controls directly. This is precisely the Mediator shape: the controls themselves never reference each other, only the controller (mediator) does, even though frameworks rarely use the word "Mediator" in their documentation.

public class RegistrationController {
    @FXML private CheckBox termsCheckbox;
    @FXML private TextField emailField;
    @FXML private Button submitButton;

    @FXML
    private void onFieldChanged() {
        submitButton.setDisable(!(termsCheckbox.isSelected() && emailField.getText().contains("@")));
    }
}

49. Compare the Mediator pattern with a middleware or interceptor chain: how does a chain of independent interceptors differ from a single centralized Mediator?

An interceptor or middleware chain processes a single request through a linear sequence of independent handlers, each of which can inspect, modify, or short-circuit the request before passing it to the next, but no interceptor coordinates the others or knows the full chain; each only knows "call the next one." Mediator, by contrast, is a single hub with a global view of all colleagues and decides which colleague, if any, should react, not necessarily in a fixed linear order.

A useful distinction: a chain is good for a pipeline of independent, composable transformations applied to one thing (logging, authentication, compression), while Mediator is good for coordinating peer-to-peer reactions among a fixed set of distinct object types that need to know about each other's state changes.

Linear pipeline vs central hub

50. Describe migrating a legacy codebase where UI components hold direct references to a dozen sibling components into a Mediator-based design. What is the migration strategy?

Start by cataloguing every direct call currently made between components, since this map becomes the mediator's required coordination logic. Introduce a mediator interface and a first ConcreteMediator implementation that, initially, just forwards to the existing direct calls unchanged, so behavior is preserved exactly while the seams are being introduced.

Then migrate one pair of components at a time: replace their direct reference with a call into the mediator, move the corresponding coordination logic into the mediator's implementation, and run the existing test suite (or add characterization tests first) after each migrated pair before moving to the next, rather than attempting the whole rewrite in one pass.

Strangler-style migrationIncremental, test-covered steps

51. Explain how the Mediator pattern reduces the number of associations from roughly O(n squared) to O(n) as more colleague types are added.

If every pair of n colleague classes potentially needs to interact directly, the number of possible pairwise associations grows proportionally to n times (n-1), which is O(n squared); a system with ten interdependent widgets can require up to ninety directional references. With a mediator, each of the n colleagues needs exactly one association, to the mediator, so the total number of associations grows linearly, O(n), regardless of how many of those colleagues actually need to interact with each other.

Complexity reductionScales linearly with colleagues

52. Walk through a worked example showing the O(n squared) direct-reference explosion in a five-widget dialog before introducing a Mediator.

With five widgets, textbox, checkbox, list, submit button, and a status label, each one potentially reacting to up to four others, a direct-reference design can require as many as twenty directional associations to wire by hand, and every new widget added to the dialog means revisiting some subset of the existing five to add new references.

// Direct-reference design: up to 5 x 4 = 20 potential associations to maintain
class Textbox  { Checkbox c; ListBox l; Button b; Label s; }
class Checkbox { Textbox t; ListBox l; Button b; Label s; }
// ...and so on for ListBox, Button, Label

After introducing a single DialogMediator, the same five widgets each hold exactly one reference, to the mediator, five associations total instead of twenty, and the coordination rules live in one place instead of scattered across five classes.

53. Discuss the performance considerations of routing every colleague interaction through a single mediator versus direct calls. Does Mediator add meaningful overhead?

A hand-written mediator that simply dispatches to the correct colleague method adds one extra virtual call and, at most, a small conditional or switch to decide which colleague to invoke, overhead the JIT compiler routinely optimizes away once the call site is hot, so in an ordinary in-process application the extra indirection is negligible.

The overhead becomes meaningful only when the mediator does something non-trivial per interaction, holding a broad lock across its entire coordination method, performing blocking I/O inline, or maintaining a growing registry data structure with poor lookup characteristics, none of which are inherent to Mediator itself but are implementation choices worth profiling under real load before assuming Mediator is the bottleneck.

54. Explain how to log and observe interactions passing through a Mediator for debugging and auditing purposes without polluting colleague classes with logging code.

Because every coordinated interaction already passes through the mediator's single entry point, adding structured logging there captures a complete audit trail of "who triggered what, and what the mediator decided to do about it," without touching a single colleague class.

class ChatRoom implements ChatMediator {
    private static final Logger log = LoggerFactory.getLogger(ChatRoom.class);

    @Override
    public void send(User sender, String message) {
        log.info("mediator dispatch: sender={} recipients={}", sender.getName(), users.size() - 1);
        for (User user : users) {
            if (user != sender) user.receive(sender.getName(), message);
        }
    }
}
Centralized observability

55. Discuss security considerations when a Mediator coordinates access between components with different privilege levels.

Because the mediator sits between every colleague, it is a natural, and sometimes overlooked, place to enforce authorization: verifying that a low-privilege colleague's requested interaction with a high-privilege colleague is actually permitted before routing the call, rather than trusting every colleague to self-police its own requests.

The risk is the inverse: if the mediator is treated purely as plumbing and never checks permissions, it can become a confused-deputy channel that lets a colleague indirectly trigger a privileged action it could never have called directly, simply by routing the request through the trusted mediator.

Design note If colleagues run with different trust levels, make the mediator an explicit authorization checkpoint, not just a dumb router.

56. How would you document a Mediator's "protocol", the set of interactions it supports, so new developers can safely add colleagues?

Document, for each event or notification the mediator accepts, which colleague types can send it, what state the mediator inspects to decide a reaction, and which colleague types can be affected as a result, essentially a small interaction table separate from the Javadoc of any single method. This matters because the mediator's real behavior lives in the combination of several colleagues' interactions, which is not obvious from reading any one colleague class in isolation.

/**
 * Protocol:
 *  - "field-changed" from Textbox or Checkbox -> re-evaluates Submit button enabled state
 *  - "submit-clicked" from Button -> validates all fields, then calls OrderService
 * New colleague types must be added to this list when they participate in coordination.
 */

57. Explain how dependency injection frameworks, such as Spring, are typically used to wire a ConcreteMediator and its colleagues together at startup.

The mediator and its colleagues are declared as beans, and the mediator's constructor or setters receive the colleague beans it needs to coordinate, letting the DI container handle the wiring order rather than colleagues constructing each other by hand. Colleagues declare a dependency on the Mediator interface type, and the container injects whichever concrete mediator bean is configured.

@Configuration
class DialogConfig {
    @Bean
    DialogMediator dialogMediator(Checkbox terms, Textbox email, Button submit) {
        return new RegistrationDialog(terms, email, submit);
    }
}

58. Compare constructor injection of the mediator into colleagues versus a setter-based or two-phase registration approach where a colleague is constructed first and then registered with the mediator afterward.

Constructor injection, passing the mediator into each colleague's constructor, guarantees a colleague can never exist in an invalid state without its mediator reference, which is preferable whenever the colleague and mediator can be constructed in a known order, typically mediator-then-colleagues or colleague-then-mediator with the mediator taking colleagues as constructor arguments as in the chat room example.

A two-phase approach, constructing the colleague first and calling mediator.register(colleague) afterward, is necessary when colleagues must be created before the mediator exists, or when colleagues can be added and removed dynamically at runtime, such as UI components created and destroyed as a user navigates a wizard; the trade-off is a brief window where the colleague exists but is not yet registered, which callers must be careful not to rely on.

59. Discuss how event sourcing interacts with a Mediator-coordinated system. Can mediator interactions themselves be recorded as an event log?

Because every coordinated interaction already passes through the mediator, it is a natural single point to append each interaction as an immutable event to a durable log, effectively turning the mediator into the write path of an event-sourced system. Replaying that log later can reconstruct the exact sequence of coordination decisions, which is valuable for debugging, auditing, or rebuilding derived state after a bug fix.

class EventSourcedMediator implements Mediator {
    private final EventStore eventStore;

    @Override
    public void notify(Object sender, String event) {
        eventStore.append(new MediatorEvent(sender.getClass().getSimpleName(), event, Instant.now()));
        dispatch(sender, event);
    }
}

60. Explain how the Mediator pattern applies to elevator-system coordination: multiple Elevator colleagues coordinated by a single Dispatcher mediator to avoid conflicting floor assignments.

Individual Elevator instances do not decide among themselves who answers a hall call; each reports its current floor and availability to a central Dispatcher mediator, and the dispatcher alone decides which elevator should respond to a given request, based on proximity, direction of travel, and current load, information no single elevator has about its peers.

class Dispatcher implements ElevatorMediator {
    private final List<Elevator> elevators;

    @Override
    public void requestFloor(int floor, Direction direction) {
        Elevator best = elevators.stream()
            .min(Comparator.comparingInt(e -> e.estimatedTimeTo(floor, direction)))
            .orElseThrow();
        best.dispatchTo(floor);
    }
}

61. Walk through implementing an air-traffic control tower Mediator in Java, including how it prevents two Aircraft colleagues from being cleared to land on the same runway simultaneously.

Extending the Q4 sketch, the tower's requestLanding method must be a single, synchronized decision point so that two concurrent requests cannot both observe the runway as free and both proceed; only one aircraft's request should ever succeed until that aircraft reports having landed and cleared the runway.

class Airport implements ControlTower {
    private Aircraft runwayOccupant;

    @Override
    public synchronized boolean requestLanding(Aircraft aircraft) {
        if (runwayOccupant != null) {
            aircraft.instructToHold(); // mediator tells the colleague what to do next
            return false;
        }
        runwayOccupant = aircraft;
        aircraft.instructToLand();
        return true;
    }
}
Mutual exclusion via mediator

62. Explain how the Mediator pattern applies to a chat application's "typing indicator" and "user joined or left" notifications, coordinated through the ChatRoom mediator rather than broadcast directly between User objects.

A "typing" or presence event follows the exact same shape as a chat message: the originating User calls chatRoom.notifyTyping(this) or chatRoom.userJoined(this), and the mediator decides how to fan that out, potentially applying different rules than plain messages, such as throttling how often typing indicators are rebroadcast, or excluding a user who just left from further notifications.

@Override
public void notifyTyping(User user) {
    for (User other : users) {
        if (other != user) other.showTypingIndicator(user.getName());
    }
}

63. Discuss how you would extend a chat-room Mediator example to support private, direct messages between two specific Users, while still routing every message through the mediator.

A private message is still sent through the mediator, just with an explicit recipient rather than "everyone except the sender"; the mediator looks up the recipient by name or id from its own registry and delivers to that single user, still without the sender ever holding a direct reference to the recipient object.

@Override
public void sendPrivate(User sender, String recipientName, String message) {
    users.stream()
        .filter(u -> u.getName().equals(recipientName))
        .findFirst()
        .ifPresentOrElse(
            recipient -> recipient.receive(sender.getName(), message),
            () -> sender.receive("system", "user not found: " + recipientName));
}

64. Explain the risk of message ordering guarantees when a Mediator dispatches to multiple colleagues asynchronously, and how to preserve ordering when it matters.

If a mediator hands each colleague notification off to a thread pool or asynchronous executor instead of calling colleagues synchronously in a loop, two messages sent from the same colleague in quick succession can arrive at another colleague out of order, since there is no guarantee the executor processes submitted tasks in submission order across all recipients.

When ordering matters, such as chat messages from the same sender needing to appear in the order they were sent, either dispatch synchronously per sender, use a single-threaded executor per recipient so that recipient's deliveries are strictly ordered, or attach a sequence number to each message so recipients can detect and correct out-of-order delivery themselves.

Async dispatch pitfallPer-recipient ordering

65. Describe how you would handle a slow or unresponsive colleague inside a Mediator's dispatch loop without blocking delivery to the other colleagues.

A naive dispatch loop that calls each colleague's method synchronously in sequence means one slow or hung colleague delays delivery to every colleague that comes after it in the iteration order. Dispatching each colleague's notification on its own asynchronous task, with a bounded timeout, isolates a slow colleague's latency from the rest.

@Override
public void send(User sender, String message) {
    for (User user : users) {
        if (user == sender) continue;
        CompletableFuture.runAsync(() -> user.receive(sender.getName(), message), dispatchExecutor)
            .orTimeout(2, TimeUnit.SECONDS)
            .exceptionally(ex -> { log.warn("delivery failed for {}", user.getName(), ex); return null; });
    }
}

66. Explain how a Mediator can apply a timeout or circuit breaker around calls to a colleague that may be slow or failing.

Wrapping each colleague call in a timeout, as in Q65, protects against occasional slowness, but a colleague that fails repeatedly and quickly should trip a circuit breaker so the mediator stops calling it for a cooldown period rather than repeatedly waiting on (and failing against) a colleague known to be unhealthy, protecting the mediator's own throughput for the remaining healthy colleagues.

class CircuitBreakingMediator implements Mediator {
    private final Map<Object, CircuitBreaker> breakers = new ConcurrentHashMap<>();

    @Override
    public void notify(Object sender, String event) {
        CircuitBreaker breaker = breakers.computeIfAbsent(sender, s -> new CircuitBreaker());
        if (breaker.isOpen()) return; // skip a known-unhealthy colleague
        breaker.tryCall(() -> dispatch(sender, event));
    }
}
Resilience patterns

67. Discuss testing strategies for verifying a Mediator correctly handles a colleague that throws an exception during a coordinated interaction.

Configure a mock colleague to throw when its method is called, invoke the mediator, and assert both that the exception does not silently abort delivery to other colleagues that were not yet notified, and that it does not propagate uncaught out of the mediator in a way that would crash an unrelated caller.

@Test
void continuesDispatchAfterOneRecipientThrows() {
    User failing = mock(User.class);
    doThrow(new RuntimeException("boom")).when(failing).receive(anyString(), anyString());
    User healthy = mock(User.class);
    ChatRoom room = new ChatRoom();
    room.register(failing);
    room.register(healthy);

    room.send(mock(User.class), "hi");

    verify(healthy).receive(anyString(), eq("hi")); // still delivered despite the other's failure
}

68. Explain code review red flags that indicate a Mediator-based design is deteriorating, such as colleagues referencing a concrete ConcreteMediator instead of the interface.

Beyond the Q34 bypass bug, watch for colleagues typed against the concrete ConcreteMediator class rather than the Mediator interface, since this silently defeats the ability to swap mediator implementations or mock the mediator in colleague unit tests, even though no direct colleague-to-colleague reference exists.

Also flag a mediator whose constructor keeps growing new colleague parameters (a signal it may need splitting per Q11), coordination logic duplicated between the mediator and a colleague instead of living solely in the mediator, and any colleague method whose name suggests it is making a decision that affects other colleagues, that logic likely belongs in the mediator instead.

Code review checklist

69. Discuss how to keep a ConcreteMediator's constructor from becoming an unmanageable list of colleague dependencies as the system grows.

If a mediator's constructor accumulates a dozen colleague parameters, that is often an early signal the mediator is coordinating more than one cohesive concern and should be split, per Q11 and Q13, rather than simply refactored to take a builder or parameter object, which would hide the smell without fixing it.

Where the colleague set genuinely is large but cohesive, such as many identical colleague instances (many User objects in a chat room), prefer a runtime register(colleague) method over constructor injection of every instance, since the set of colleagues is naturally dynamic rather than a fixed, enumerable list.

70. Explain how the Mediator pattern relates to the broader "hub and spoke" integration architecture used in enterprise application integration.

Hub-and-spoke integration places one central hub between every pair of systems that need to exchange data, so each system integrates once with the hub instead of once per system it needs to talk to, exactly the O(n squared) to O(n) reduction Mediator provides at the object level, applied instead to entire applications or systems as the "colleagues."

The hub in this architecture plays the mediator's role: translating formats, routing messages to the correct destination system, and centralizing the integration logic that would otherwise be duplicated across every pairwise point-to-point connection.

EAI hub-and-spokeMediator at system scale

71. Compare a point-to-point integration architecture between five microservices with a hub-and-spoke Mediator-style integration layer.

Point-to-point integration among five services can require up to twenty directional connections if each needs to call several others, each with its own network client, retry logic, and contract to maintain; adding a sixth service means potentially wiring several new direct connections into existing services. A hub-and-spoke layer, whether a hand-rolled integration mediator or a message broker, requires each of the five services to integrate with the hub exactly once, and a sixth service added later integrates only with the hub, not with the other five.

The trade-off mirrors Q14: the hub becomes a critical, and potentially complex, piece of shared infrastructure that must be operated reliably, in exchange for a dramatically simpler integration surface for every individual service.

72. Explain how an Enterprise Service Bus historically played the Mediator role at an architectural level, and why lighter-weight event buses have partly replaced it.

An Enterprise Service Bus centralized routing, transformation, and orchestration logic between many enterprise systems, functioning as a heavyweight, architecture-scale ConcreteMediator: every connected system talked only to the bus, and the bus held the rules for how messages should be translated and routed onward.

Lighter-weight event buses and message brokers have displaced much of the classic ESB's role because ESBs tended to accumulate business logic inside the bus itself, exactly the God Object risk described in Q10 but at infrastructure scale, making the bus a complex, hard-to-change bottleneck; modern architectures increasingly prefer keeping the broker itself "dumb" (pure routing and delivery) and pushing transformation and business logic back out into the services that own it.

73. Discuss the difference between a Mediator that reacts only to explicit method calls from colleagues versus one that also polls or actively queries colleague state.

A purely reactive mediator only ever does work in response to a colleague explicitly calling into it, such as notify(sender, event); it never reaches out to check a colleague's state on its own initiative. A polling mediator instead periodically queries each colleague's current state to decide whether a coordinated action is needed, useful when colleagues cannot reliably push notifications, such as legacy colleagues that expose only getters.

Reactive mediators are simpler and lower-latency since they act immediately on the triggering event, while polling mediators add latency proportional to the poll interval but tolerate colleagues that were never designed to call out to a mediator at all.

74. Explain how the Mediator pattern can help implement validation rules that depend on the combined state of multiple form fields, such as enabling submit only if both terms-accepted and email-valid are true.

A joint validation rule inherently needs to see the state of more than one colleague at once, which is awkward for any single colleague to compute on its own without reaching into its siblings. The mediator, which already holds references to every relevant colleague, is the natural place to evaluate the combined condition each time any one of the inputs changes.

@Override
public void widgetChanged(String widgetId) {
    boolean valid = termsCheckbox.isChecked() && isValidEmail(emailBox.getText());
    submitButton.setEnabled(valid);
}

75. Walk through a Java example where a Mediator disables a Submit button until two other checkbox and textbox colleagues satisfy a joint validation rule.

This is the same pattern as Q5 and Q74, made explicit end-to-end: both the checkbox and the textbox call the mediator on every change, and the mediator's single evaluation method is the only place the joint condition is expressed.

class SignupDialog implements DialogMediator {
    private final Checkbox terms = new Checkbox(this);
    private final Textbox email = new Textbox(this);
    private final Button submit = new Button();

    @Override
    public void widgetChanged(String widgetId) {
        submit.setEnabled(terms.isChecked() && email.getText().contains("@"));
    }
}

Neither Checkbox nor Textbox knows the Button exists; both simply announce their own change and trust the mediator to work out the consequence.

76. Explain how a Mediator differs from simply making all colleague classes implement a shared "listener" interface and manually wiring each pair together.

Manually wiring listeners between every pair that needs to communicate, even through a shared listener interface, still leaves the number of registrations growing with the number of interacting pairs, and each colleague still ends up holding references to the specific siblings it registered with, reintroducing the coupling Mediator exists to remove, just via an interface instead of a concrete type.

A true Mediator design has every colleague register with, and only with, the single mediator object; the mediator alone tracks who needs to hear about what, so colleagues never hold references to each other under any interface at all.

77. Discuss the impact of Mediator on unit-testing colleague classes in isolation. Does depending only on the Mediator interface make Colleague tests easier or harder to write?

It makes colleague tests substantially easier: since a colleague depends only on the narrow Mediator interface, a test can supply a trivial fake or a Mockito mock as that dependency and verify the colleague calls the expected mediator method with the expected arguments, with no need to construct any sibling colleague or the full coordination logic at all.

@Test
void checkboxNotifiesMediatorOnToggle() {
    DialogMediator mediator = mock(DialogMediator.class);
    Checkbox checkbox = new Checkbox(mediator);

    checkbox.toggle();

    verify(mediator).widgetChanged("terms");
}

78. Explain how a Mediator can be combined with the Strategy pattern so that the coordination policy itself, such as which colleague gets priority, is pluggable.

Instead of hardcoding a priority or dispatch rule inside the mediator, the mediator holds a reference to a Strategy object that encapsulates just that decision, letting the same mediator be reused with different coordination policies swapped in at construction time or even at runtime.

interface DispatchStrategy { Elevator choose(List<Elevator> elevators, int floor); }

class Dispatcher implements ElevatorMediator {
    private final DispatchStrategy strategy; // pluggable coordination policy

    @Override
    public void requestFloor(int floor, Direction direction) {
        strategy.choose(elevators, floor).dispatchTo(floor);
    }
}
Mediator + Strategy

79. Design a customer-support ticket routing system where a Mediator coordinates between Agent, Ticket, and Queue colleague objects.

A TicketRouter mediator receives a new Ticket, checks the Queue for its priority and category, and assigns it to whichever Agent colleague is available and best matched, without any Agent, Ticket, or Queue object needing a reference to another.

class TicketRouter implements SupportMediator {
    private final List<Agent> agents;

    @Override
    public void routeTicket(Ticket ticket) {
        agents.stream()
            .filter(a -> a.isAvailable() && a.handles(ticket.category()))
            .findFirst()
            .ifPresentOrElse(agent -> agent.assign(ticket), () -> escalate(ticket));
    }
}

80. Explain how the Mediator pattern applies to a ride-sharing dispatch system coordinating Driver and Rider colleague objects through a central Dispatcher mediator.

A Rider requests a trip through the Dispatcher mediator without knowing which drivers exist or where they are; the dispatcher matches the request against available Driver colleagues based on proximity and status, and instructs the chosen driver directly. Neither the rider nor the driver ever holds a reference to the other; all coordination, including cancellation and re-matching if a driver declines, happens through the dispatcher.

Marketplace matching as Mediator

81. Discuss how you would version a Mediator's interface as the interactions it needs to support evolve, without breaking existing colleague implementations.

Prefer adding new methods with default implementations (for interfaces) rather than changing existing method signatures, so existing colleague and mediator implementations continue compiling unchanged; a colleague that does not need the new interaction simply never calls the new method.

interface DialogMediator {
    void widgetChanged(String widgetId);
    default void widgetRemoved(String widgetId) {} // added later, existing implementers unaffected
}

For a broad event-based interface, adding a new event type or record variant is naturally backward compatible for senders, but a sealed-type dispatch (Q39) requires every mediator implementation to add a new switch arm, which the compiler will flag, turning what could be a silent runtime gap into a compile-time to-do list.

82. Explain the difference between a stateless Mediator, pure routing logic with no stored state, and a stateful Mediator that tracks ongoing interaction state, such as a saga's current step.

A stateless mediator, such as a simple chat room that only fans a message out to currently registered users, holds no memory of past interactions between calls; each dispatch is fully determined by its arguments and the current colleague registry alone. A stateful mediator, such as the air-traffic control tower tracking its runway occupant, or a saga orchestrator tracking which step a given order is currently on, must persist and correctly update that state across calls, and that state itself becomes something to protect from race conditions and corruption.

Stateful mediators are considerably harder to test and reason about, since the same call can produce different results depending on prior calls, and they typically need explicit consideration of what happens if the mediator process restarts mid-workflow.

83. Walk through implementing a stateful Mediator that tracks a multi-step approval workflow's current step across several Approver colleague objects.

The mediator stores, per in-flight approval request, which step it is currently waiting on; when an Approver colleague calls back with a decision, the mediator checks whether that approver was actually the one currently expected to act, then advances the stored step and notifies the next approver, or finalizes the request if it was the last step.

class ApprovalWorkflowMediator implements ApprovalMediator {
    private final Map<String, Integer> currentStep = new ConcurrentHashMap<>();
    private final List<Approver> approvers;

    @Override
    public void approve(String requestId, Approver approver) {
        int step = currentStep.getOrDefault(requestId, 0);
        if (approvers.get(step) != approver) return; // not this approver's turn
        int next = step + 1;
        if (next >= approvers.size()) { finalizeApproval(requestId); return; }
        currentStep.put(requestId, next);
        approvers.get(next).notifyPendingApproval(requestId);
    }
}

84. Explain common mistakes developers make when first implementing Mediator, such as letting the mediator directly mutate colleague private fields instead of calling colleague methods.

A frequent beginner mistake is exposing package-private or public fields on colleague classes so the mediator can read and write them directly, rather than calling well-named public methods; this breaks encapsulation and makes the colleague's own invariants (such as "value must never be blank") impossible to enforce inside the colleague itself.

// Wrong: mediator reaches into colleague internals
submitButton.enabled = valid;

// Right: mediator calls a colleague's public method, colleague enforces its own invariants
submitButton.setEnabled(valid);

Another common mistake is putting so little logic in colleague classes that they become anemic data holders with all real behavior siphoned into the mediator, which is a milder form of the God Object risk from Q10, applied even to a small dialog.

85. Discuss how access modifiers and package-private visibility can help enforce that Colleague classes only ever interact through the Mediator, not directly with each other.

Placing colleague classes and their mediator in the same package, then declaring colleague constructors or mutating methods with package-private (default) visibility rather than public, lets the mediator, which lives in the same package, call them freely while preventing code outside the package, including sibling colleagues if they were ever tempted, from constructing or mutating a colleague directly.

package dialog;

class Checkbox { // package-private class: only usable within the dialog package
    Checkbox(DialogMediator mediator) { ... } // package-private constructor
}
Encapsulation via package boundaries

86. Explain how the Mediator pattern can coordinate multiple independent state machines, such as parallel approval branches in a workflow, so they don't directly reference one another.

Each state machine reports its own transitions to the mediator rather than to the other state machines, and the mediator holds the cross-branch rules, such as "the final approval step cannot proceed until both the legal branch and the finance branch have reached their terminal state." Individual state machines remain fully unaware that a parallel branch even exists.

@Override
public void branchTransitioned(String branchId, String newState) {
    branchStates.put(branchId, newState);
    if (allBranchesReachedTerminalState()) {
        finalApprovalStep.begin();
    }
}

87. Compare Mediator with a plain shared "context" object that colleagues read and write without any coordination logic. Why is a context object not really a Mediator?

A shared mutable context object that every colleague reads and writes freely removes direct colleague-to-colleague references, superficially resembling Mediator, but it has no coordination logic of its own: it is a passive data bag, not an active participant deciding what should happen in response to a change. Any coordination rule, "if X changes, Y should react," still has to live somewhere, and with a plain context object it usually ends up duplicated across every colleague that needs to check the context's state, which is exactly the duplication Mediator exists to eliminate.

A true Mediator is distinguished by owning behavior, not just shared state: it actively decides and drives what colleagues should do next, rather than merely being a place they all happen to read from and write to.

Passive state vs active coordination

88. Explain how to add structured logging and metrics to a Mediator so you can measure how often each type of interaction it coordinates occurs, for capacity planning.

Because every coordinated interaction passes through the mediator's entry point, incrementing a counter or timer keyed by event type there gives an accurate picture of interaction volume and latency without instrumenting every colleague individually.

@Override
public void notify(Object sender, String event) {
    meterRegistry.counter("mediator.events", "type", event).increment();
    Timer.Sample sample = Timer.start(meterRegistry);
    dispatch(sender, event);
    sample.stop(meterRegistry.timer("mediator.dispatch.duration", "type", event));
}

This data is directly useful for capacity planning, since it reveals which interaction types dominate load and whether a specific coordinated path is trending toward becoming the bottleneck described in Q35.

89. Discuss designing a Mediator for a real-time multiplayer game server coordinating Player colleague objects' moves and turn order.

A game session mediator receives each player's proposed move, validates it against the current shared game state, applies it if legal, and broadcasts the updated state to all other players, all without any Player object needing a reference to another player's connection or state. This centralization is also what makes server-authoritative validation possible: the mediator, not any individual client-facing player object, is the single source of truth for whether a move is legal.

Server-authoritative coordination

90. Explain how a Mediator can decouple a plugin system, where each Plugin colleague only talks to a central PluginManager mediator rather than to other plugins directly.

Plugins are written independently, often by different authors, and cannot reasonably hold compile-time references to each other since neither knows which other plugins will be installed alongside it. A PluginManager mediator lets each plugin publish events or expose capabilities to the manager, and the manager routes those to whichever other plugins have registered interest, without any plugin ever importing another plugin's class.

91. Walk through a Java example of a PluginManager mediator that lets one Plugin publish an event that other Plugins can react to, without any plugin holding a reference to another.

Each Plugin is given only the PluginManager mediator at initialization; it publishes events through the manager and optionally registers to receive events of a given type, entirely unaware of which concrete plugin classes, if any, are listening.

interface PluginManager {
    void publish(String eventType, Object payload);
    void subscribe(String eventType, Consumer<Object> handler);
}

class DefaultPluginManager implements PluginManager {
    private final Map<String, List<Consumer<Object>>> handlers = new HashMap<>();

    @Override
    public void publish(String eventType, Object payload) {
        handlers.getOrDefault(eventType, List.of()).forEach(h -> h.accept(payload));
    }

    @Override
    public void subscribe(String eventType, Consumer<Object> handler) {
        handlers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler);
    }
}

92. Explain how the Mediator pattern applies within a single microservice's internal domain layer, such as an internal domain event dispatcher, versus at the cross-service architecture level.

Inside one microservice, a domain event dispatcher (often backed by something like Spring's ApplicationEventPublisher from Q21) lets internal domain objects and application services coordinate without direct references, exactly like an in-process Mediator, and every interaction stays within one transaction boundary and one deployable unit.

At the cross-service level, the equivalent role is played by a message broker or orchestrator (Q23, Q25), which must additionally handle network failures, serialization, and eventual rather than immediate consistency; the underlying coordination idea is identical, but the engineering concerns multiply once the "colleagues" are independently deployed services rather than objects in one JVM.

93. Discuss how to prevent a Mediator's internal collection of registered colleagues from leaking memory when colleagues are dynamically created and destroyed, such as UI components.

If colleagues register with a long-lived mediator but are never explicitly unregistered when they are destroyed, such as a dialog closed by the user while the mediator instance persists for the application's lifetime, the mediator's registry keeps a strong reference to each colleague indefinitely, preventing it from being garbage collected even though nothing else in the application still needs it.

The straightforward fix is to require an explicit unregister(colleague) call at the colleague's teardown point, typically the same lifecycle hook that disposes of the UI component or closes the resource, mirrored against wherever register was originally called.

94. Explain how weak references can be used in a Mediator's colleague registry to avoid memory leaks from colleagues that are never explicitly unregistered.

Storing colleagues behind WeakReference entries, or using a weak-keyed structure, lets the garbage collector reclaim a colleague once nothing else in the application holds a strong reference to it, even if the developer forgot to call an explicit unregister method, at the cost of the mediator needing to periodically purge cleared references and tolerate a colleague silently disappearing from its registry.

private final List<WeakReference<Colleague>> colleagues = new ArrayList<>();

void notifyAll(String event) {
    colleagues.removeIf(ref -> ref.get() == null); // purge collected colleagues
    for (WeakReference<Colleague> ref : colleagues) {
        Colleague c = ref.get();
        if (c != null) c.onEvent(event);
    }
}
Trade-off Weak references trade a hard requirement (remembering to unregister) for a softer guarantee (eventual collection); prefer explicit unregistration when the colleague's lifecycle is well-defined and only fall back to weak references when it genuinely is not.

95. Walk through refactoring a "God Object" Mediator that has grown to coordinate fifteen unrelated colleague types into several smaller, focused mediators.

Start by grouping the fifteen colleague types by which ones actually interact with each other versus which merely happen to live in the same class because that was convenient at the time; unrelated colleague groups that never influence each other's behavior are the easiest and safest candidates to split first.

Introduce one new, narrow mediator interface per cohesive group, migrate that group's colleagues to depend on the new interface instead of the original monolith, and only once every group has been migrated should the original God Object mediator class be deleted, verified at each step by the existing test suite (or characterization tests written first if coverage is thin).

96. Explain how you would introduce a Mediator into an existing tightly-coupled codebase incrementally, without a large upfront rewrite.

Pick the single most painful pair, or small cluster, of tightly-coupled classes first, typically the one that breaks most often when either side changes, and introduce a narrow mediator interface covering just that interaction, leaving the rest of the codebase's direct references untouched for now. Verify the behavior is unchanged with tests before moving on.

Repeat for the next most painful cluster, reusing the same mediator if the new cluster's colleagues genuinely interact with the first cluster's, or introducing a separate mediator if they do not, per the splitting guidance in Q11. This incremental approach delivers value (a decoupled, tested seam) after every step rather than requiring the whole system to be mid-rewrite before any benefit is realized.

Incremental adoption

97. Discuss how a code reviewer should evaluate a pull request that introduces a new Mediator class. What questions should they ask?

Ask whether every colleague in the change depends only on the mediator interface, not on the concrete mediator class or on sibling colleagues (Q34, Q68); whether the mediator's constructor dependency list is a manageable, cohesive set rather than an early sign of a future God Object (Q10, Q69); and whether the interaction protocol the mediator implements is documented somewhere a future developer adding a colleague would actually find it (Q56).

Also check whether the PR includes tests that exercise the mediator's coordination logic with mocked colleagues (Q28), rather than only testing colleagues in isolation, since the coordination logic is the entire reason the mediator exists and is the part most likely to contain the actual bug if one is introduced later.

98. Explain the relationship between Mediator and the broader concept of "loose coupling" in object-oriented design, and how to explain this trade-off to a non-technical stakeholder.

Loose coupling means a change to one component should require little or no change to unrelated components; Mediator is one of the most direct mechanical tools for achieving it among a set of peer objects, since it collapses many potential pairwise dependencies down to one dependency per colleague, on the mediator alone.

To a non-technical stakeholder, the useful framing is cost of change: "today, adding a new feature to this screen means carefully checking a dozen other things it might break; after this change, adding a new feature means writing one new, isolated piece and telling the coordinator about it, without touching the other eleven."

Stakeholder communication

99. Compare Mediator to a REST API gateway that sits in front of several backend services. Is an API gateway a form of Mediator?

An API gateway that only routes an incoming request to the correct backend service and returns its response, without the backend services coordinating with each other through it, is acting more like a Facade or a reverse proxy than a Mediator, since the flow is one-directional per request and the gateway is not managing bidirectional interaction between the backend services themselves.

An API gateway starts to genuinely play the Mediator role once it orchestrates a workflow across multiple backend calls in response to one request, calling service A, using A's result to decide what to send service B, and reconciling both results, which is the same orchestration shape as the saga orchestrator in Q26, just triggered synchronously by an inbound HTTP request instead of an asynchronous event.

Gateway as Facade vs Gateway as Mediator

100. As a capstone answer, explain how you would decide, for a described system with multiple interacting components, whether to reach for Mediator versus Observer, Facade, or a simple event bus.

Start by asking whether the relationship is genuinely many-to-many and bidirectional, several peer components that each need to both trigger reactions in, and be affected by, several others, rather than a strict one-directional flow. If it is purely one-directional broadcast from one source to many listeners with no coordinating decision required, Observer alone is enough. If it is a client simplifying access into a subsystem with no peer-to-peer coordination between subsystem parts, Facade is the right tool, not Mediator.

If the interactions are genuinely peer-to-peer and bidirectional but simple, one-way notifications with no complex, stateful coordination logic, a plain event bus or pub-sub mechanism (Spring events in-process, a message broker across services) is usually sufficient and avoids introducing a bespoke mediator class. Reach for a dedicated hand-written Mediator specifically when the coordination logic between peers is complex or stateful enough that it deserves to be named, tested, and reasoned about as its own first-class piece of the design, and be ready to explain, honestly, how you would keep that mediator from growing into the God Object described back in Q10.

Decision frameworkInterview capstone
No comments
Leave a Comment