Strategy
Strategy packages a group of interchangeable algorithms into their own classes and lets the object using them switch between algorithms at runtime through a shared interface.
Problem
Imagine you're building a navigation app. Depending on how the traveler gets around - on foot or by car - the route between two points has to be calculated differently: a walking route favors footpaths and shortcuts, while a road route must follow streets and traffic rules.
You could implement all these options with a giant `if/else` block inside a `Navigator` class. But every time a new travel mode is added - cycling, public transit - you have to modify that class, violating the Open/Closed Principle and making it harder to maintain.
As the number of travel modes grows, the class becomes bloated with unrelated routing logic that is difficult to test in isolation.
Solution
The Strategy pattern suggests extracting all these algorithms (route calculations) into separate classes called strategies. The original class, called the context, stores a reference to one of the strategy objects and delegates the work to it.
The context is not responsible for selecting the appropriate algorithm - the client does that. The context works with all strategies through a generic interface exposing a single method that triggers the algorithm.
This makes the context independent of concrete strategies, so you can add new travel modes without ever touching the `Navigator` class.
When to Use
- Reach for Strategy when an object needs to pick between several variants of a behavior and that choice may change while the program runs.
- It also helps when several classes are nearly identical except for one piece of behavior - that piece becomes the strategy, and the classes collapse into one.
- Use it to keep an algorithm's implementation details out of a class whose main job is unrelated business logic.
Real-World Examples
- **Payment gateways** - checkout can switch between Stripe, PayPal, or Apple Pay without changing the cart logic.
- **`Array.prototype.sort()`** - the comparator function you pass is a strategy that defines how elements are ordered.
- **File compression** - archiving tools let users choose between ZIP, GZIP, or BZIP2 without changing the archiving UI.
Structure
The Context holds a reference to a Strategy and delegates work to it. The Strategy interface is common to all supported algorithms. ConcreteStrategy classes implement each variation of the algorithm.
Participants
Holds a reference to a strategy object and delegates all algorithm-specific work to it. Communicates with the strategy only through the Strategy interface.
In the diagram: Navigator.
Declares an interface common to all supported versions of the algorithm. The context uses this interface to call the algorithm defined by a concrete strategy.
In the diagram: RouteStrategy.
Implements a specific version of the algorithm using the Strategy interface.
In the diagram: WalkingRoute, RoadRoute.
Creates a specific strategy object and passes it to the context. The context exposes a setter that lets clients replace the strategy at runtime.
In the diagram: Client.
class RouteStrategy {
calculate(from, to) {
throw new Error('calculate() must be implemented');
}
}
class WalkingRoute extends RouteStrategy {
calculate(from, to) {
return `Walking route from ${from} to ${to} via footpaths`;
}
}
class RoadRoute extends RouteStrategy {
calculate(from, to) {
return `Driving route from ${from} to ${to} via roads`;
}
}
class Navigator {
#strategy;
constructor(strategy) {
this.#strategy = strategy;
}
setStrategy(strategy) {
this.#strategy = strategy;
}
buildRoute(from, to) {
return this.#strategy.calculate(from, to);
}
}
const navigator = new Navigator(new WalkingRoute());
console.log(navigator.buildRoute('Home', 'Park'));
navigator.setStrategy(new RoadRoute());
console.log(navigator.buildRoute('Home', 'Park'));
Step 1 of 5
RouteStrategy declares the interchangeable algorithm's interface
The base `calculate(from, to)` throws - it exists only so `Navigator` has one method signature to call, regardless of which concrete strategy is plugged in.
Step 2 of 5
WalkingRoute and RoadRoute are two interchangeable algorithms
Both `extends RouteStrategy` and implement `calculate` with different wording, but the exact same signature - `Navigator` can hold either one without knowing which.
Step 3 of 5
Navigator is the context - it delegates to whatever strategy it holds
`#strategy` is set in the constructor and can be swapped via `setStrategy`; `buildRoute` just calls `this.#strategy.calculate(from, to)` - the navigator itself has no routing logic at all.
Step 4 of 5
The navigator starts with WalkingRoute and produces a walking route
`new Navigator(new WalkingRoute())` injects the strategy at construction time, so `buildRoute('Home', 'Park')` resolves to `WalkingRoute.calculate`'s footpath message.
Step 5 of 5
setStrategy swaps the algorithm without touching buildRoute
`navigator.setStrategy(new RoadRoute())` replaces `#strategy`, so the very same `buildRoute('Home', 'Park')` call now returns `RoadRoute.calculate`'s driving message instead.
interface RouteStrategy {
calculate(from: string, to: string): string;
}
class WalkingRoute implements RouteStrategy {
calculate(from: string, to: string): string {
return `Walking route from ${from} to ${to} via footpaths`;
}
}
class RoadRoute implements RouteStrategy {
calculate(from: string, to: string): string {
return `Driving route from ${from} to ${to} via roads`;
}
}
class Navigator {
private strategy: RouteStrategy;
constructor(strategy: RouteStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: RouteStrategy): void {
this.strategy = strategy;
}
buildRoute(from: string, to: string): string {
return this.strategy.calculate(from, to);
}
}
const navigator = new Navigator(new WalkingRoute());
console.log(navigator.buildRoute('Home', 'Park'));
navigator.setStrategy(new RoadRoute());
console.log(navigator.buildRoute('Home', 'Park'));
interface RouteStrategy {
String calculate(String from, String to);
}
class WalkingRoute implements RouteStrategy {
public String calculate(String from, String to) {
return "Walking route from " + from + " to " + to + " via footpaths";
}
}
class RoadRoute implements RouteStrategy {
public String calculate(String from, String to) {
return "Driving route from " + from + " to " + to + " via roads";
}
}
class Navigator {
private RouteStrategy strategy;
Navigator(RouteStrategy strategy) {
this.strategy = strategy;
}
void setStrategy(RouteStrategy strategy) {
this.strategy = strategy;
}
String buildRoute(String from, String to) {
return strategy.calculate(from, to);
}
}
Navigator navigator = new Navigator(new WalkingRoute());
System.out.println(navigator.buildRoute("Home", "Park"));
navigator.setStrategy(new RoadRoute());
System.out.println(navigator.buildRoute("Home", "Park"));
interface IRouteStrategy
{
string Calculate(string from, string to);
}
class WalkingRoute : IRouteStrategy
{
public string Calculate(string from, string to) => $"Walking route from {from} to {to} via footpaths";
}
class RoadRoute : IRouteStrategy
{
public string Calculate(string from, string to) => $"Driving route from {from} to {to} via roads";
}
class Navigator
{
private IRouteStrategy strategy;
public Navigator(IRouteStrategy strategy) => this.strategy = strategy;
public void SetStrategy(IRouteStrategy strategy) => this.strategy = strategy;
public string BuildRoute(string from, string to) => strategy.Calculate(from, to);
}
var navigator = new Navigator(new WalkingRoute());
Console.WriteLine(navigator.BuildRoute("Home", "Park"));
navigator.SetStrategy(new RoadRoute());
Console.WriteLine(navigator.BuildRoute("Home", "Park"));
from __future__ import annotations
from abc import ABC, abstractmethod
class RouteStrategy(ABC):
@abstractmethod
def calculate(self, start: str, end: str) -> str: ...
class WalkingRoute(RouteStrategy):
def calculate(self, start: str, end: str) -> str:
return f'Walking route from {start} to {end} via footpaths'
class RoadRoute(RouteStrategy):
def calculate(self, start: str, end: str) -> str:
return f'Driving route from {start} to {end} via roads'
class Navigator:
def __init__(self, strategy: RouteStrategy) -> None:
self._strategy = strategy
def set_strategy(self, strategy: RouteStrategy) -> None:
self._strategy = strategy
def build_route(self, start: str, end: str) -> str:
return self._strategy.calculate(start, end)
navigator = Navigator(WalkingRoute())
print(navigator.build_route('Home', 'Park'))
navigator.set_strategy(RoadRoute())
print(navigator.build_route('Home', 'Park'))
Step 1 of 5
RouteStrategy declares the interchangeable algorithm's interface
`calculate(start, end)` is `@abstractmethod` - it exists only so `Navigator` has one method signature to call, regardless of which concrete strategy is plugged in.
Step 2 of 5
WalkingRoute and RoadRoute are two interchangeable algorithms
Both subclass `RouteStrategy` and implement `calculate` with different wording, but the exact same signature - `Navigator` can hold either one without knowing which.
Step 3 of 5
Navigator is the context - it delegates to whatever strategy it holds
`_strategy` is set in `__init__` and can be swapped via `set_strategy`; `build_route` just calls `self._strategy.calculate(start, end)` - the navigator itself has no routing logic at all.
Step 4 of 5
The navigator starts with WalkingRoute and produces a walking route
`Navigator(WalkingRoute())` injects the strategy at construction time, so `build_route('Home', 'Park')` resolves to `WalkingRoute.calculate`'s footpath message.
Step 5 of 5
set_strategy swaps the algorithm without touching build_route
`navigator.set_strategy(RoadRoute())` replaces `_strategy`, so the very same `build_route('Home', 'Park')` call now returns `RoadRoute.calculate`'s driving message instead.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- New algorithms can be added as new strategy classes, without editing the context or any existing strategy.
- The active algorithm can be swapped while the program is running, simply by handing the context a different strategy object.
- Each algorithm becomes independently testable, since it lives in its own class with no dependency on the context's internals.
- A tangled chain of conditionals gets replaced by composition: the context simply holds and delegates to an object.
Disadvantages
- For a handful of algorithms that almost never change, a strategy hierarchy adds more ceremony than a simple conditional would.
- The calling code has to know which strategies exist and what each one is for, so the decision logic doesn't disappear - it just moves to the client.
- In languages with first-class functions, a single-method strategy interface can often be replaced by passing a plain function or closure instead.
Question 1 of 10
What problem does a giant if/else block inside Navigator cause as travel modes are added?
Correct! Every time a new travel mode is added, you have to modify that class, violating the Open/Closed Principle, and the class becomes bloated with unrelated routing logic that's hard to test in isolation.
Not quite. Every time a new travel mode is added, you have to modify that class, violating the Open/Closed Principle, and the class becomes bloated with unrelated routing logic that's hard to test in isolation.
Question 2 of 10
Does Strategy make the decision logic about which algorithm to use disappear?
Correct! The calling code has to know which strategies exist and what each one is for, so the decision logic doesn't disappear - it just moves to the client.
Not quite. The calling code has to know which strategies exist and what each one is for, so the decision logic doesn't disappear - it just moves to the client.
Question 3 of 10
What does Strategy package into their own classes?
Correct! Take the navigation app: instead of one giant if/else inside Navigator for walking, driving, and later cycling, each route-building algorithm gets its own class behind a shared interface, and Navigator just calls whichever one is currently plugged in.
Not quite. Take the navigation app: instead of one giant if/else inside Navigator for walking, driving, and later cycling, each route-building algorithm gets its own class behind a shared interface, and Navigator just calls whichever one is currently plugged in.
Question 4 of 10
According to the pros listed for Strategy, what happens to a tangled chain of conditionals?
Correct! A tangled chain of conditionals gets replaced by composition: the context simply holds and delegates to an object, rather than branching on type internally.
Not quite. A tangled chain of conditionals gets replaced by composition: the context simply holds and delegates to an object, rather than branching on type internally.
Question 5 of 10
What is a stated alternative to a single-method strategy interface in languages with first-class functions?
Correct! In languages with first-class functions, a single-method strategy interface can often be replaced by passing a plain function or closure instead.
Not quite. In languages with first-class functions, a single-method strategy interface can often be replaced by passing a plain function or closure instead.
Question 6 of 10
What role does the client play once a strategy has been handed to the context?
Correct! The context exposes a setter that lets clients replace the strategy at runtime, so the client can create a strategy object, pass it to the context, and swap it out later.
Not quite. The context exposes a setter that lets clients replace the strategy at runtime, so the client can create a strategy object, pass it to the context, and swap it out later.
Question 7 of 10
How does the context interact with strategies?
Correct! The context works with all strategies through a generic interface exposing a single method that triggers the algorithm, making it independent of concrete strategies.
Not quite. The context works with all strategies through a generic interface exposing a single method that triggers the algorithm, making it independent of concrete strategies.
Question 8 of 10
In the navigation-app example, why does a walking route need to be calculated differently from a road route?
Correct! A walking route favors footpaths and shortcuts, while a road route must follow streets and traffic rules, which is exactly the kind of variation Strategy is meant to isolate into separate classes.
Not quite. A walking route favors footpaths and shortcuts, while a road route must follow streets and traffic rules, which is exactly the kind of variation Strategy is meant to isolate into separate classes.
Question 9 of 10
Who selects the appropriate strategy in the Strategy pattern?
Correct! The context is not responsible for selecting the appropriate algorithm - the client does that.
Not quite. The context is not responsible for selecting the appropriate algorithm - the client does that.
Question 10 of 10
What is a stated drawback of Strategy for a handful of algorithms that almost never change?
Correct! For a handful of algorithms that almost never change, a strategy hierarchy adds more ceremony than a simple conditional would.
Not quite. For a handful of algorithms that almost never change, a strategy hierarchy adds more ceremony than a simple conditional would.