skip to content

When should you design an API to accept a Strategy (like Comparator) versus using inheritance/Template Method, and what are the trade-offs of behavior-as-a-parameter?

level: principalimportance: should knowfreq 40%

answer

  1. Strategy = pass behavior in (composition); Template Method = override hooks (inheritance)
  2. Strategy wins: runtime choice, combinable, one axis, shallow hierarchy
  3. Template Method wins: fixed skeleton, closed family, needs base state
  4. Lambdas make Strategy cheap → JDK tilts to behavior-as-parameter
  5. Costs: indirection, no context internals, parameter soup, caller contract

basics

~20 s

Use a Strategy (pass behavior in, like a Comparator) when callers need to vary one step at runtime and combine behaviors freely. Use inheritance/Template Method when the variation is a fixed family known at design time. Strategy favors composition over subclassing.

solid answer

~50 s

Strategy externalizes a varying algorithm as an object the caller supplies — the JDK's sort taking a Comparator is the canonical case. Choose Strategy when: the behavior varies at runtime, callers should combine or reuse behaviors (Comparator's thenComparing), you want to avoid a subclass explosion, and you favor composition over inheritance. Template Method instead fixes the overall algorithm in a base class and lets subclasses override hook steps; choose it when the skeleton is invariant and variations form a closed, compile-time set tightly coupled to the base. Strategy's trade-offs: it adds an interface and indirection, the behavior can't easily see the context's private state (only what's passed), and over-parameterizing an API ('flag/strategy soup') hurts readability. With Java's functional interfaces the cost is low, so modern API design leans heavily toward behavior-as-parameter — Comparator, Stream operations, Predicate filters — over inheritance hierarchies. A good rule: pass a strategy for the *one* axis that genuinely varies; don't turn every step into a parameter.

go deeper

for a junior

Can say Strategy means passing behavior in (like a Comparator) rather than subclassing, and that lambdas make it easy.

for a middle

Contrasts Strategy with inheritance/Template Method and can name when each fits at a basic level (runtime variation vs fixed family).

for a senior

Articulates the decision factors (runtime choice, composability, single axis, statelessness) and the main trade-offs (indirection, no context internals, contract burden).

for a principal

Frames API design choices around composition vs inheritance, reasons about discoverability, contract enforcement, megamorphic-call/JIT and allocation costs, and avoids parameter-soup — using Comparator as the model of a well-chosen strategy axis.

## Two ways to let behavior vary When part of an algorithm needs to differ across uses, you have two classic options: ### Strategy (composition / behavior-as-parameter) Define the varying step as an interface and have the caller **pass an implementation in**. The 'context' holds a reference and delegates. JDK example: `Collections.sort(list, comparator)` — the sort is fixed, the ordering is supplied. ### Template Method (inheritance) Define the full algorithm's **skeleton** in a base class as a `final` method that calls overridable **hook** steps; subclasses override the hooks to vary specific steps. The shape of the algorithm is fixed in the base; subclasses fill in blanks. JDK-ish example: `AbstractList` providing iteration scaffolding over an abstract `get(int)`/`size()`. ## How to choose Prefer **Strategy** when: 1. **Runtime variation.** The behavior is chosen while the program runs (user clicks a column → pick a Comparator), not fixed by which subclass was instantiated. 2. **Open-ended / combinable behaviors.** Callers should build new behaviors from existing ones — `comparing(...).thenComparing(...).reversed()`. Inheritance can't compose like this; you'd need a new subclass per combination (a combinatorial explosion). 3. **One varying axis, many contexts.** The same strategy plugs into many places (a Comparator works in sort, TreeSet, PriorityQueue, Stream.sorted). 4. **You want to avoid deep hierarchies.** 'Favor composition over inheritance' (Effective Java): strategies keep classes shallow and decoupled. 5. **Stateless, side-effect-free step.** The varying step needs only its inputs, not the context's internals. Prefer **Template Method** when: 1. **The skeleton is the invariant and the point.** You're enforcing an *order of steps* (e.g. open → process → close) that callers must not reorder. 2. **Closed, compile-time family.** The set of variants is known and small, and each is tightly bound to the base's protected state. 3. **Hooks need privileged access** to the base's protected members. The two aren't exclusive: a Template Method's hook can itself accept a Strategy. ## Why Java's design tilts toward Strategy Before Java 8, Strategy meant an interface plus a class (or anonymous class) per behavior — real boilerplate, so inheritance/Template Method often won by inertia. **Functional interfaces + lambdas** collapsed that cost: a strategy is now a one-line lambda or method reference. Consequently the JDK and idiomatic Java pervasively use behavior-as-parameter: `Comparator`, `Predicate` in `removeIf`, `Function` in `map`, `Consumer` in `forEach`, `Supplier` for laziness. This is Strategy at scale — passing functions as values. ## Trade-offs and failure modes of behavior-as-parameter 1. **Indirection / discoverability.** A strategy is invisible in the type of the context; readers must trace what was passed. A subclass name documents intent more loudly. 2. **Limited access to context state.** The strategy sees only what's passed to it (e.g. the two elements in `compare`). If it needs the context's internals, Strategy is awkward and Template Method (with protected hooks) fits better. 3. **Parameter soup.** Turning *many* steps into strategy parameters yields call sites with several lambdas — hard to read and easy to mis-order. Parameterize only the axis that genuinely varies. 4. **Contract burden on the caller.** The caller's strategy must honor the interface's contract (Comparator's total-order rules); a bad strategy can break the context (TimSort's 'violates general contract'). Inheritance can enforce more via the base. 5. **Statelessness expectation.** Strategies are usually expected to be stateless and reusable; a stateful lambda capturing mutable data invites concurrency and reuse bugs. 6. **Performance.** Negligible for most cases; capturing lambdas may allocate, and megamorphic call sites (many different lambdas through one sort) can dent JIT inlining in hot loops — rarely a real concern. ## A practical heuristic - Vary **one** thing, at **runtime**, that should be **combinable** and needs only its inputs → **Strategy** (a functional interface, ideally). - Enforce a **fixed skeleton** with a **closed** set of step-overrides needing **base state** → **Template Method**. - Don't parameterize everything; each strategy parameter is a contract the caller must satisfy and a reader must follow. `Comparator` is the exemplar of getting this right: a single varying axis (ordering), runtime-chosen, richly combinable, contract-bounded, stateless — exactly where Strategy shines.

  • Why did lambdas shift idiomatic Java toward Strategy over inheritance?
    They removed the class-per-behavior boilerplate, making a strategy a one-line lambda. So passing behavior as a parameter (Comparator, Predicate, Function) became cheaper than scaffolding subclass hierarchies — composition over inheritance in practice.
  • Give a case where Template Method beats Strategy.
    When an invariant step order must be enforced and the variant steps need the base class's protected state — e.g. a request-processing skeleton (validate→process→audit) where subclasses override process using protected helpers and must not reorder the steps.

saying these in an interview costs you the question

  • Claiming Strategy and inheritance are interchangeable with no trade-offs
  • Parameterizing every step into strategies ('lambda soup') and calling it clean design
  • Using Strategy when the hook genuinely needs the context's private/protected state
  • Forgetting the caller must honor the strategy's contract (e.g. Comparator total order)
  • Assuming Template Method is obsolete — it's still right for fixed skeletons

context