AcademyDesign Patterns
Strategy pattern
Swapping an algorithm without touching the class that uses it, the interface every strategy must implement even for a one-off, and reaching for it before a second case exists.
6 min readUpdated 3 exercises
Strategy answers the most direct question among the eleven behavioral patterns: how to swap an algorithm without rewriting the class that uses it.
A Strategy pulls "how do I do this" out of a class and into a separate, interchangeable piece, a function or an object with one method, that the class calls without caring which one it got handed. The class that uses the strategy never grows an if for every variant; it just calls whatever it was given.
The problem it solves
You sell subscriptions and compute a discount. There is a student discount, a yearly-plan discount, a coupon code, and a Black Friday sale. A first version puts everything in one function:
function price(base: number, type: string): number {
if (type === 'student') return base * 0.5;
if (type === 'yearly') return base * 0.8;
if (type === 'blackfriday') return base * 0.7;
return base;
}- Every new rule edits the same function. It grows, and each edit risks the others.
- Rules cannot be tested alone. They are tangled with the branching.
- The choice is fixed in code. You cannot pass in a rule from configuration or from a test.
The fix is to make each rule its own function and let the caller pick which one to hand in.
Structure
Three parts:
- The strategy interface: one operation, such as
(arr) => sorted. - Concrete strategies: the interchangeable implementations (
ascending,descending). - The context (
Sorter): holds a strategy and calls it.
class Sorter {
constructor(strategy) {
this.strategy = strategy;
}
sort(arr) {
return this.strategy(arr);
}
}
const ascending = (arr) => [...arr].sort((a, b) => a - b);
const descending = (arr) => [...arr].sort((a, b) => b - a);
// new Sorter(ascending).sort([3, 1, 2]) → [1, 2, 3]
// new Sorter(descending).sort([3, 1, 2]) → [3, 2, 1]Sorter has no idea which strategy it holds, and never branches on it. Adding byLength means writing one function, and nothing else changes.
Where it is used in real systems
| Use case | The interchangeable algorithms |
|---|---|
| Authentication | Passport strategies: local, Google, GitHub, JWT. |
| Pricing and discounts | Student, yearly, coupon, sale. |
| Sorting and comparison | The compare function passed to Array.sort. |
| Payment processing | Card, bank transfer, wallet. |
| Validation | Different rules for different countries or plans. |
| Compression and encoding | gzip, brotli or none, based on the client. |
| Caching | Least recently used, time based, or none. |
| Routing and retries | Fixed delay, exponential backoff, or no retry. |
The clue is a switch or a chain of if statements on a "type" that picks between ways of doing the same job.
Strategies you already use
- `array.sort(compareFn)`. The compare function is a strategy.
- `array.map`, `filter` and `reduce` take the varying part as a function.
- Passport.js. Each login method is a strategy plugged into the same
authenticatecall. - Retry libraries. Backoff policies passed as options.
- React render props and hooks. A component that takes a function deciding how to render or sort.
- Prisma and ORMs choosing a dialect, or bundlers choosing a loader per file type.
Strategy in JavaScript is often just a function
In languages with first-class functions, you rarely need a class. Pass the function. A map of named strategies keeps it tidy and open to extension:
type Discount = (base: number) => number;
const discounts: Record<string, Discount> = {
student: (base) => base * 0.5,
yearly: (base) => base * 0.8,
none: (base) => base,
};
function price(base: number, kind: keyof typeof discounts): number {
return discounts[kind]?.(base) ?? base;
}Reach for a class when a strategy carries state or several related methods. Otherwise, a function is enough.
Strategy, State and Template Method
| Who picks | Changes over time | Where the variation lives | |
|---|---|---|---|
| Strategy | The client | Rarely | A separate object passed in |
| State | The object itself | Often | A separate object that replaces itself |
| Template Method | The class hierarchy | Never | A subclass overriding a step |
If you are choosing how something is done, and can choose again from outside, it is Strategy.
When not to use it
- Only two variants that will never grow. An
ifis simpler. - Callers must know the details. If picking a strategy requires understanding how each works, the choice is not really encapsulated.
- Too many tiny strategies. A file per one-line function is noise. Keep small ones together.
- The variation is data, not behavior. If only a number changes (a rate of 0.2 instead of 0.5), pass the number.
What interviewers ask
- "What is the Strategy pattern?" Defining a family of interchangeable algorithms and letting the client choose one at runtime.
- "How does it remove `if/else`?" The branching moves to where the strategy is chosen, and the code that uses it just calls it.
- "Strategy versus State?" Same shape. Strategy is chosen from outside and stays. State changes itself over time.
- "Give a JavaScript example." The
Array.sortcompare function, or Passport strategies. - "How does it help testing?" Each strategy is tested alone, and the context is tested with a fake strategy.
The mistake to avoid
A strategy chosen by an if inside the context. If Sorter.sort starts with if (this.strategy === ascending), the context knows about specific strategies again and the pattern has been undone. The context should only call the strategy. Any special case belongs in the strategy itself.
The mechanism above, passing in the behavior that varies instead of branching on it, is the same in JavaScript, Python or Java. Only the syntax changes.
Now practice it
1 challenge · 2 quizzesReading this page is the cheap half. These are the exercises that make you use it.
- StrategyProPlug two interchangeable algorithms into one class that never checks which one it got — Strategy with the "no if/else chain" rule enforced by the tests.ChallengeSolve · EasyChallengeSolve · Easy
- Design Patterns · Behavioral PatternsTen quick questions across Strategy, State, Observer, Command and the rest of the behavioral family, to check the mental model before this one.QuizTake · HardQuizTake · Hard
- Design Patterns · Behavioral Patterns — Series 2ProA second, tougher pass on the same behavioral family, for once the first set feels easy.QuizTake · HardQuizTake · Hard
Read next
State pattern
Letting an object swap what it can do next, decided from inside itself, the state class and transition table it costs, and two states that disagree who owns a transition.
6 min
Template Method pattern
Fixing the step order of an algorithm in a base class while subclasses fill in the rest, the subclassing it costs to change one step, and a base class open to rewrites.
6 min