skip to content

Observer, Mediator, and Chain of Responsibility all decouple a sender from a receiver. How does each one route a message differently, and when would you choose one over the others?

level: seniorimportance: should knowfreq 54%

answer

  1. Observer = fan-out to all, no reply, order-agnostic
  2. Mediator = hub owns the rules, O(N) edges instead of O(N²)
  3. Chain = ordered, one consumer, short-circuit
  4. Mediator's risk: god object; Observer's risk: lapsed listener leak
  5. Chain vs Decorator: one consumes vs all contribute

basics

~20 s

Observer broadcasts a change to every registered listener. Mediator puts one coordinator in the middle so components talk to it instead of each other. Chain of Responsibility passes a request down a line of handlers until one handles it.

solid answer

~60 s

All three break a direct sender→receiver reference, but the routing semantics differ. Observer is one-to-many fan-out: the subject notifies all registered observers of a state change, does not know who they are, expects no reply, and each observer is independent. Mediator is many-to-many collapsed into a hub: colleagues send to the mediator, which knows the interaction rules and directs the right calls to the right colleagues — it centralizes coordination logic rather than eliminating it. Chain of Responsibility is one-to-one-of-many, ordered: a request travels along a chain until a handler consumes it (or nobody does), so ordering and short-circuiting are the point. Choose Observer for event notification where listeners are optional and unknown (UI updates, cache invalidation, domain events). Choose Mediator when a cluster of components has grown a tangle of mutual references and the interaction rules deserve one named owner (a dialog's field-enablement rules, an air-traffic controller). Choose Chain when a request must be tried against ordered candidates with the possibility of short-circuit (HTTP middleware, auth filters, exception handlers). The costs mirror the routing: Observer gives untraceable control flow and lifecycle leaks, Mediator risks becoming a god object, Chain makes behaviour depend on a fragile ordering.

go deeper

for a junior

Give the one-line routing difference: broadcast to many, hub in the middle, pass down a line until handled.

for a middle

Add concrete examples (UI events, filter chains, dialog coordination) and the main cost of each.

for a senior

Discuss push vs pull, sync vs async notification, chain ordering fragility, god-object risk, and error/lifecycle edge cases.

for a principal

Map them onto system-level architecture — choreography vs orchestration, event-driven fan-out vs gateway middleware — and reason about observability, failure isolation, and how untraceable control flow affects a team's ability to change the system.

## The shared problem All three are behavioral patterns that break a **direct compile-time reference** from the object that produces something to the object that consumes it. Without them you get *hardwired coupling*: A calls B, C, and D by name, so adding E means editing A. What differs is the **routing semantics** — how many receivers, in what order, with what reply, and who owns the routing knowledge. ## Observer — one-to-many broadcast **Roles.** A *subject* (or publisher) keeps a list of *observers* (subscribers) implementing a small interface. When the subject's state changes it calls `update()` (or `onEvent(e)`) on each. **Semantics.** Fan-out to all. The subject knows nothing about who listens, expects no return value, and cannot depend on ordering. Adding a listener requires no change to the subject. **Design choices inside Observer.** - **Push vs pull** — push sends the changed data in the notification (fast, but couples the payload shape); pull sends only "something changed" and observers query the subject back (looser, but N extra reads and possible inconsistency if state moved on). - **Synchronous vs asynchronous** — synchronous notification means one slow or throwing observer stalls or breaks the subject's transaction; asynchronous decouples timing but loses ordering guarantees and transactional atomicity. - **Reentrancy** — an observer that mutates the subject during notification can trigger nested notification or a concurrent-modification error over the listener list. Defensive implementations iterate over a snapshot. **Costs.** *Lapsed listener leak*: an observer that never unregisters keeps a strong reference alive, a classic memory leak. Control flow becomes untraceable — "who reacts to this event?" is not answerable from the call site. Cascading notifications can produce surprising ordering and even infinite loops. **Note.** Observer is in-process and direct. **Publish/Subscribe with a message broker** adds a middleman, persistence, and cross-process delivery; it is a distributed relative, not the same pattern. ## Mediator — many-to-many through a hub **Roles.** *Colleagues* hold a reference to a *mediator* instead of to each other; the mediator holds them all and encodes the interaction rules. **Semantics.** A colleague reports an event *to the mediator*, which decides which other colleagues to call and in what order. Crucially the mediator **knows the participants** and the rules — that knowledge is centralized, not eliminated. **When it earns its place.** N components with mutual references cost O(N²) edges; routing through a hub makes it O(N). Classic examples: a form dialog where changing one field enables/disables others, an air-traffic controller sequencing aircraft, a chat room routing messages, or a UI controller coordinating widgets. **Costs.** The mediator accumulates every rule and becomes a **god object** — the complexity was moved, not removed. Colleagues are decoupled from each other but newly coupled to the mediator. When the mediator grows past comprehension the fix is usually to split it by cohesive interaction cluster. **Observer vs Mediator.** Observer is fire-and-forget notification with no coordination logic; Mediator contains coordination logic. They compose: a mediator often *uses* Observer to hear from colleagues, then applies rules. ## Chain of Responsibility — ordered, short-circuiting hand-off **Roles.** Each *handler* holds a reference to the next and implements `handle(request)`. It either processes the request and stops, processes and forwards, or forwards untouched. The chain is assembled by the client. **Semantics.** One-to-one-of-many, **ordered**, with short-circuit. The sender does not know which handler will act — possibly none, which is an explicit design consideration (unhandled request: return a default, throw, or silently drop). **Examples.** HTTP middleware / servlet filter chains, Spring Security filter chains, logging level handlers, event bubbling in a DOM, approval workflows with escalating limits, exception handler resolution. **Costs.** Behaviour depends on **assembly order**, which lives far from the handlers themselves — a common source of subtle bugs (auth after logging leaks credentials). Debugging means walking the chain. No guarantee of handling. Long chains add latency and stack depth. **Chain vs Decorator.** Structurally identical linked wrappers. Decorator's intent is that *every* layer contributes to a result; Chain's intent is that *one* layer consumes the request and stops. ## A decision checklist | Question | Answer → pattern | |---|---| | Should all interested parties react, independently? | Observer | | Do interactions between several known components need rules in one place? | Mediator | | Should exactly one of an ordered set of candidates handle it, with short-circuit? | Chain of Responsibility | | Does the sender need a result back? | Not Observer — Chain or Mediator (or a plain call) | | Does order matter? | Chain (yes), Observer (must not rely on it) | | Is the receiver set open and unknown at build time? | Observer | ## Edge cases worth mentioning - **Observer with ordering requirements is a smell** — if listener A must run before B, you actually want a chain or an explicit pipeline. - **Error handling** differs sharply: a throwing observer can abort a broadcast unless the subject isolates each callback; a throwing chain handler kills the request; a throwing colleague surfaces inside the mediator's rule. - **Testing**: Observer needs assertions on emitted notifications; Chain needs assertions on which handler consumed and that the rest were untouched; Mediator can be unit-tested directly as the rule owner, which is one of its underrated advantages. - **Distributed variants**: Observer→pub/sub broker, Chain→gateway middleware or a saga's ordered steps, Mediator→orchestrator (versus choreography, which is the Observer-style alternative). The trade-offs carry over: orchestration centralizes and is traceable; choreography decentralizes and is opaque.

  • Your Observer-based system now requires listener A to run before listener B. What does that tell you?
    That Observer is the wrong pattern for that part. Observer's contract is unordered, independent fan-out; encoding priority into it (priority fields, sorted listener lists) reintroduces coupling. Model the ordered part explicitly as a pipeline or Chain of Responsibility and keep Observer for the genuinely independent reactions.
  • How do you stop a Mediator from turning into a god object?
    Split it by cohesive interaction cluster rather than by component — one mediator per workflow or per screen region, each with a narrow colleague set. Keep it rule-routing only; push actual domain computation back into the colleagues. If it still grows, that is a signal the components' responsibilities are wrong, not just the mediator's.
  • In a Chain of Responsibility, what should happen when no handler accepts the request?
    It must be a deliberate decision, not an accident. Options: a terminal default handler that always accepts, throwing an explicit unhandled-request error, or returning an empty/absent result the caller must handle. Silent dropping is the dangerous default because it fails invisibly.

Observer is a mailing list — the sender blasts an announcement and never learns who read it. Mediator is an air-traffic controller — pilots never talk to each other, they talk to the tower, which owns the rules. Chain of Responsibility is a support escalation ladder — tier 1 either resolves your ticket or passes it up, and only one tier ends up owning it.

saying these in an interview costs you the question

  • Saying Observer guarantees notification order — it explicitly does not.
  • Equating Mediator with 'removes coupling' — it relocates coupling to the hub.
  • Describing Chain of Responsibility as broadcasting to all handlers; its point is that one handles and stops.
  • Confusing in-process Observer with a message broker's publish/subscribe (durability, cross-process, retries).
  • Forgetting observer deregistration, causing the lapsed-listener memory leak.
  • Claiming Decorator and Chain of Responsibility are the same pattern because the code looks alike.

context