Strategy Pattern
Put each way of doing a job in its own object behind one interface, so the caller can swap the way without changing its own code.
Overview
The Strategy pattern is one of the behavioral patterns in Design Patterns (Gamma, Helm, Johnson and Vlissides, 1994). When there are several ways to do one job, such as several ways to price shipping, sort a list or compress a file, you give each way its own class with the same interface. The code that needs the job done, the context, holds one of those objects and calls it. Which one it holds can be decided when the context is built, or changed later, and the context's code never changes.
Strategy is probably the most used pattern in everyday code, often without the name. Every sort that takes a comparator or a key function is a strategy. Interviewers ask about it to see whether you can replace a growing chain of conditionals with objects, whether you know when a plain function is enough, and whether you can tell Strategy from the patterns that look like it: State and Template Method.
The problem it solves
A checkout computes the shipping cost. First there is a flat rate. Then a per-kilo rate for heavy parcels. Then free shipping over a threshold, then a rate per country. Each rule is an if in one growing function; each new rule means editing and re-testing that function, and the rules cannot be tested on their own.
- All rules in one function — One small class per rule
- A new rule edits tested code — A new rule is a new class; the context is untouched
- Rules cannot be tested alone — Each strategy is tested by itself
- The rule is fixed by the branch that runs — The rule is an object that can be chosen, stored or passed around
Structure
- Strategy interface — The one method every way of doing the job provides — ShippingStrategy.cost()
- Concrete strategies — One class per way — FlatRate, PerKilo, FreeOverThreshold
- Context — Holds a strategy and delegates to it — Checkout
- Client — Picks the strategy and gives it to the context — the setup code
A runnable example
The checkout never changes while the strategy changes three times. The third strategy wraps another one, which shows that strategies compose.
How the strategy gets into the context
- Constructor — Checkout(FlatRate()) — The choice is fixed for the object's life
- Setter or attribute — checkout.strategy = PerKilo(1.8) — The choice changes while the object lives
- Method argument — checkout.total(goods, kg, strategy) — The choice differs per call
- Lookup table — STRATEGIES[name] — The choice comes from data: a config value, a request field
A default strategy in the constructor keeps callers simple, and a lookup table keyed by a name is how you connect a strategy to configuration without a conditional.
Where you already use it
- Python sorted() and list.sort() — the key function — Called exactly once per element; sorts are guaranteed stable
- Java Collections.sort, Arrays.sort — a Comparator — A functional interface, so a lambda is a strategy
- Java sorted sets and maps — a Comparator — Controls the order of a TreeSet or TreeMap
What you will be able to do
- Turn a conditional chain into strategies and show that the context stops changing.
- Draw the structure and name the four participants.
- Choose how a strategy is injected: constructor, setter, argument or lookup table.
- Decide when a function is enough and when a class is worth it.
- Separate Strategy from Template Method and State in one sentence each.
Interview questions
- Show how Strategy removes a conditional chain. What does it cost?
- How is Strategy different from Template Method?
- How is Strategy different from State? The class diagrams look identical.
- When is a function enough, and when do you want a strategy class?
- Design a pricing system with several discount rules that must combine.
Sign up free to read the full topic
More Low-Level Design topics
Start free on Cariara · Coding problems · System design studio