Skip to main content

Patternbeginner

Strategy

Overview

Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.

It is the pattern that turns up in more places in application code than any other, almost always in the degenerate form of a function passed as an argument. And it is where the ceremony of the full version — interface, implementations, selection — most often charges without delivering: with two variants nobody is going to extend, an if says the same thing in fewer lines.

Problem

An operation has several ways of being carried out, and the choice depends on context.

Without the pattern, that becomes a conditional that grows:

if type == A: algorithm A
else if B: algorithm B
else if C: algorithm C

Three problems appear when that conditional grows.

It replicates: other operations need the same distinction, and the switch appears in several places. Adding a case touches all of them.

It mixes levels: the selection logic and the implementations live together, and the method gets long.

And it prevents variation at runtime: the choice is in the code, not in the data.

Core Concepts

The structure

Only the context's arrow is solid — the other two mark implementation, not use.

The context holds a strategy and delegates. Swapping the strategy swaps the behaviour without touching the context.

Strategy is composition over inheritance applied

What Template Method does with inheritance, Strategy does with composition — and so it inherits the advantages: variation at runtime, no coupling to the base's implementation, and multiple combinable axes.

In languages with first-class functions, it is a function

When the strategy has a single method, the interface is ceremony. Passing a function solves the same thing with less code:

calculate(amount, rate -> amount * rate)

That is not a minor simplification — it is the form the pattern takes in most modern code, and the reason "Strategy" appears less often as an explicit name even while being used constantly.

Where the selection lives

The pattern does not say who chooses the strategy. Three options, with different consequences: the client chooses and injects; a factory chooses from a piece of data; or configuration defines it.

The second merely moves the switch into the factory — which is a real improvement (it exists once, not in every operation), but does not eliminate it.

When to Use

  • There are real variants of an algorithm, with more than one implementation in use.
  • The choice has to happen at runtime or by configuration.
  • The conditional replicates in more than one place.
  • New variants appear frequently.
  • Each variant needs to be tested in isolation.

When Not to Use

When there are two stable variants. An if is more readable than an interface and two classes. The pattern pays off from three onwards, and mainly when the number grows.

When the variants never change. With no future variation, the flexibility is never exercised.

When the conditional appears only once. There is no replication to eliminate.

When the language offers first-class functions and the strategy has one method. Use the function.

When the strategies need different data. If each requires distinct parameters, the common interface becomes a set of optional parameters — and the pattern is forcing a uniformity that does not exist.

Alternatives

  • A function as a parameter — the modern form of the same pattern.
  • A simple conditional — for two stable variants.
  • A dispatch table — a map from key to function, when the selection is by value.
  • Polymorphism on the object itself — if the variation follows the data's type, the method can live there.

Trade-offs

StrategyConditional
A new variant does not touch the existing onesTouches the conditional
Each variant testable in isolationTests go through the conditional
Choice at runtimeFixed in the code
More types and more filesEverything in one place
Indirect flowReadable linearly
The selection still has to live somewhereThe selection is the conditional itself

Failure Modes

Interface with optional parameters. A sign that the strategies are not uniform.

Stateful strategy. If it keeps state between calls, sharing it between contexts produces a defect.

Explosion of trivial classes. Twenty one-line strategies.

Scattered selection. The switch you wanted to eliminate reappears in the several places that choose the strategy.

Common Mistakes

Applying it with two variants. Three files to replace a two-line if, and the third variant that would justify the structure never arrives.

Creating an interface where a function suffices.

Thinking it eliminates the conditional. It is moved into the selection; the gain is that it exists once.

Confusing it with State. Strategy is chosen from outside; State changes on its own as the object evolves.

Where it appears in practice

Sort comparators. sort(list, comparator) is Strategy: the comparison algorithm is passed in. In modern languages, a function.

Encoding and compression. Libraries that accept the algorithm as a parameter.

Retry policies. Fixed wait, exponential, with jitter — each a strategy, chosen by configuration.

Shipping, tax and discount calculation. The most common use in business systems, and the one that usually justifies the pattern: the variants are many, they change by external decision, and they need to be tested in isolation.

In the first three, the predominant form today is the function. In the fourth, the interface is justified because each strategy usually needs more than one operation and dependencies of its own.

Real-World Example

A subscription system calculated discounts with a 200-line method and seven branches: first month, annual, coupon, referral, partnership, employee, reactivation.

The conditional existed in three places: value calculation, simulation display and the financial report. All three had diverged — the report did not know about "reactivation", added six months earlier.

Extracting into strategies resolved the divergence by construction: one place per discount type came to exist, and the three consumers use the same objects.

What the team got wrong at first and fixed later: it also created NoDiscountStrategy to "eliminate the null". It never did anything and added a case to every reading of the code. It was removed, and the absence of a discount went back to being represented by the absence of a strategy.

Not every case needs a class.

Practical Exercise

Find the longest switch or if chain in your system.

Check: does it appear in more than one place? Have the copies diverged? How many branches were added in the last year?

The three answers together say whether Strategy pays off there.

Interview Questions

  • What is the difference between Strategy and State?
  • Does Strategy eliminate the conditional?
  • When is a function preferable to an interface?

Further Exploration

  • Gamma, Erich et al. Design Patterns. Addison-Wesley, 1994.
Finished reading this document?Your progress is saved in this browser only.