skip to content

In the GRASP set of object-oriented design principles, what does the Indirection principle say, and what problem does it solve?

level: juniorimportance: must knowfreq 55%

answer

  1. extra object in the middle
  2. A → M → B, A never sees B
  3. low coupling, substitutable far side
  4. adapter / controller / mediator / gateway
  5. extra hop is the price

basics

~10 s

Indirection says: when two components would otherwise be wired directly together, put a third object between them to mediate. Neither side knows the other, so each can change or be replaced independently.

solid answer

~50 s

GRASP (General Responsibility Assignment Software Patterns) is a catalogue of principles for deciding which object should hold which responsibility. Indirection answers: "how do I avoid direct coupling between two things that should not know each other?" The answer is to assign the responsibility of mediating between them to an intermediate object. Instead of A calling B's concrete API, A talks to an intermediary M (an interface, adapter, controller, mediator, repository, gateway, broker), and M talks to B. The benefit is low coupling: A depends only on M's stable contract, so B can be swapped, moved out of process, mocked in tests, or re-versioned without touching A. The cost is one more moving part: an extra hop, an extra file to open when reading a trace, and a contract that must itself be designed and maintained. Indirection is the mechanism that underlies most decoupling patterns.

code

pseudocode · 17 lines
pseudocode
// Before: the domain speaks a vendor's language
class OrderService {
  fun place(order) {
    stripeClient.post("/v1/charges", {amount: order.cents, source: order.token})
  }
}

// After: an intermediary mediates
interface PaymentGateway { fun charge(amount: Money, method: PaymentMethod): ChargeResult }

class OrderService(private val payments: PaymentGateway) {
  fun place(order) { payments.charge(order.total, order.method) }
}

class StripeGateway(private val http: HttpClient) : PaymentGateway {
  override fun charge(amount, method) = /* translate to/from Stripe's wire format */
}

go deeper

for a junior

State the definition and give one concrete example: a service that talks to an external payment/email API through a gateway interface instead of calling the vendor SDK directly.

for a middle

Connect it to Low Coupling and name several concrete manifestations — adapter, controller, repository, facade, mediator — and mention the cost of an extra hop.

for a senior

Discuss when the indirection is earned: a named axis of change, a real second implementation, a test seam, or a cross-cutting hook — and how a leaky intermediary buys nothing.

for a principal

Frame it as a system-wide budgeting decision: where the organization draws boundaries, who owns each contract, and how speculative indirection compounds into latency, on-call debugging cost, and architecture that nobody can trace end to end.

## What GRASP is **GRASP** stands for *General Responsibility Assignment Software Patterns* (Craig Larman, *Applying UML and Patterns*). It is not a set of code templates like the Gang-of-Four design patterns; it is a set of nine **reasoning principles** for the single hardest question in object-oriented design: *which object should be given this responsibility?* The nine are: Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, **Indirection**, and Protected Variations. ## The principle, stated plainly > **Problem:** Two components need to interact, but you do not want them directly coupled — perhaps because one is unstable, external, replaceable, or simply should not know about the other. > > **Solution:** Assign the responsibility of *mediating* between them to an intermediate object, so that the two never reference each other directly. Before: ``` OrderService ────────────► StripeHttpClient ``` `OrderService` now contains Stripe's URL shapes, its JSON field names, its error codes. Swapping to another payment provider means editing `OrderService`. Testing `OrderService` means having Stripe (or a fake HTTP server) available. After: ``` OrderService ──► PaymentGateway (interface) ◄── StripePaymentGateway ──► StripeHttpClient ``` `OrderService` now depends only on the sentence *"charge this amount, get back a result"*. Stripe's vocabulary stops at the adapter. ## Vocabulary used above (defined) - **Coupling** — how much one unit must know about another. Direct coupling to a concrete class means every change in that class can ripple outward. - **Cohesion** — how focused a unit's responsibilities are. Indirection tends to *improve* cohesion too: translation logic moves out of the caller into the mediator. - **Contract / interface** — the named set of operations a caller may invoke, without saying who implements them. - **Adapter** — an intermediary whose job is *translation* between two vocabularies (your domain's words ↔ a foreign system's words). - **Mediator** — an intermediary that *coordinates* several peers so the peers don't reference each other pairwise. - **Controller** — the intermediary that receives an input event (an HTTP request, a UI click) and routes it into the domain, so the UI and the domain never touch. - **Facade / gateway / repository / proxy / broker / message queue / DNS / virtual method table** — all further instances of the same idea at different scales. ## Why it pays 1. **Substitutability.** Any implementation satisfying the contract can be dropped in — a real one in production, a stub in tests, a cached one under load. 2. **Change containment.** When the far side changes (new API version, new vendor, new wire format), only the intermediary is edited. This is the *ripple effect* being stopped at a wall. 3. **Testability.** The intermediary is the natural seam for a test double, so the caller can be unit-tested with no network, database, or clock. 4. **Cross-cutting hooks.** Once a call passes through one object, you can add retries, timeouts, caching, metrics, authorization, or logging there — in one place rather than at every call site. 5. **Protecting the domain's language.** Foreign concepts (HTTP status codes, ORM entities, vendor enums) are translated at the boundary instead of leaking through the whole codebase. ## What it costs - **One more hop** to read when debugging; stack traces and "go to definition" get longer. - **A contract to maintain.** A bad abstraction is worse than no abstraction: if the interface is just a mirror of one vendor's API (`chargeWithStripeToken(...)`), it decouples nothing. - **Possible runtime cost** if the indirection is a network hop or a serialization boundary rather than a method call. - **Speculative generality** — building the seam for a second implementation that never arrives. The well-known aphorism (David Wheeler, popularized by Butler Lampson and Kevlin Henney) captures both halves: *"All problems in computer science can be solved by another level of indirection — except the problem of too many levels of indirection."* ## Edge cases and clarifications - **Indirection is not automatically an interface.** A concrete mediating class with no interface is still indirection. Conversely, an interface with exactly one implementation that mirrors it 1:1 is often *ceremony*, not decoupling. - **Indirection is not the same as "add a layer everywhere."** It is a targeted response to a *specific* coupling you have a reason to break. - **Indirection can be dynamic.** Late binding (virtual dispatch), dependency injection, service discovery, and message brokers are indirection where the far end is chosen at runtime rather than compile time. - **Indirection is directional-agnostic.** It also breaks *cycles*: if A and B both need each other, giving both a dependency on a mediator M turns a cycle into a tree.

  • Does applying Indirection always mean introducing an interface or abstract type?
    No. The essence is an intermediate *object* that mediates; it can be a plain concrete class (a controller, a facade, a mediator). An interface is added when you additionally need multiple or swappable implementations, or when you want the caller to depend on a type it owns rather than one the far side owns.
  • How does Indirection relate to Low Coupling in GRASP?
    Low Coupling is the *goal*; Indirection is one *mechanism* for reaching it. You justify an indirection by pointing at the coupling it removes. If you cannot name the coupling that got weaker, the indirection is unearned.
  • Can indirection ever increase coupling?
    Yes — if the intermediary leaks the far side's concepts (a 'gateway' whose methods are named after vendor endpoints and whose types are vendor DTOs), then every caller is still coupled to the vendor, plus there is now an extra class. That is indirection without abstraction.

A translator in a negotiation. Neither party learns the other's language, either party can be replaced without retraining the other — but every sentence now takes twice as long, and a bad translator can distort the deal.

saying these in an interview costs you the question

  • "Indirection means adding an interface for every class" — mechanical one-to-one interfaces decouple nothing and are pure ceremony.
  • "More indirection is always better design" — each level costs traceability; the classic caveat is 'except the problem of too many levels of indirection'.
  • Confusing Indirection with inheritance or with polymorphism — polymorphism is choosing behavior by type; indirection is inserting a mediator between two parties.
  • Claiming indirection removes coupling entirely — it *relocates* coupling onto a contract you now own and must keep stable.
  • Believing indirection is only about testing/mocking — that is one benefit, not the definition.

context