Strategy Pattern: Swap the Rule, Not the System
When one task has several valid ways to run, Strategy keeps the workflow stable while the decision-making rule can change independently.
What is the Strategy pattern?
The Strategy pattern is a behavioral design pattern that defines a family of interchangeable algorithms, puts each one behind a shared interface, and lets the surrounding code pick which one to run at runtime. Instead of a growing if/else or switch block that decides how to do a task, the caller holds a reference to a strategy object and simply delegates to it.
The payoff is that the workflow and the decision rule evolve independently. Adding a new pricing model, sorting policy, or compression method becomes "write one new class that satisfies the contract" rather than "edit every branch that knows about the old options." This is the open/closed principle in its most practical form: the context is closed to modification but open to new behaviors.
The logic, step by step
A checkout, route planner, or game character keeps growing conditional branches whenever a new behavior is added.
Extract each interchangeable behavior behind one shared contract and inject the behavior the context should use.
- INPUT selects the behavior required for this situation.
- CONTEXT delegates the work through a stable strategy contract.
- STRATEGY runs its own algorithm without changing the context.
New algorithms can be added or selected without rewriting the workflow that consumes them.
Strategy pattern example in TypeScript
interface ShippingStrategy {
quote(distanceKm: number, weightKg: number): number;
}
class BicycleCourier implements ShippingStrategy {
quote(distanceKm: number): number {
return Math.min(distanceKm * 1.2, 15); // cheap, capped, short range
}
}
class ExpressCourier implements ShippingStrategy {
quote(distanceKm: number, weightKg: number): number {
return 8 + distanceKm * 2.5 + weightKg * 0.8;
}
}
class Checkout {
constructor(private shipping: ShippingStrategy) {}
total(cartTotal: number, distanceKm: number, weightKg: number): number {
// The checkout flow never changes when a new courier is added.
return cartTotal + this.shipping.quote(distanceKm, weightKg);
}
}
const rushOrder = new Checkout(new ExpressCourier());
console.log(rushOrder.total(120, 12, 3)); // 160.4 Run it in the real world
A delivery quote keeps the same checkout flow while switching between bicycle, standard courier, and express pricing strategies.
When to use it — and when not to
Reach for Strategy when
- One task has several valid algorithms and the choice depends on runtime data (user tier, locale, configuration, load).
- Conditional branches keep multiplying in one method every time the business adds an option.
- You need to unit-test algorithms in isolation from the workflow that uses them.
Skip it when
- There is exactly one behavior and no concrete plan for a second — the indirection is pure overhead.
- The "strategies" are one-liners; a plain function parameter or lambda does the same job with less ceremony.
Strategy vs related patterns
Template Method pattern — Template Method varies steps through inheritance inside a fixed sequence; Strategy varies the whole algorithm through composition. Prefer Strategy when behaviors need to change at runtime.
Command pattern — Command wraps a request (what to do, possibly with undo); Strategy wraps a policy (how to do it). A queue holds commands, a context holds one strategy.
Frequently asked questions
What problem does the Strategy pattern solve?
It removes conditional logic that selects between interchangeable algorithms. Each algorithm lives in its own class behind a shared interface, so new behaviors are added without editing the code that uses them.
Is Strategy just dependency injection?
They cooperate but are not the same thing. Strategy is the design decision to model interchangeable algorithms as objects; dependency injection is one convenient mechanism for handing the chosen strategy to the context.
Do I need classes for the Strategy pattern in JavaScript or TypeScript?
No. In languages with first-class functions, a strategy can be a plain function matching an expected signature. Use classes when strategies carry configuration or state; use functions when they are pure.