Coding · Design patterns
Strategy pattern explained with examples
Short answer
Strategy is a behavioral design pattern that defines a family of algorithms, puts each one in a separate object with a common interface, and makes them interchangeable. The code that uses an algorithm, called the context, holds a reference to a strategy and calls it without knowing which one it is. In TypeScript a strategy is often just a function, as in the comparator passed to array.sort().
Intent and the problem it solves
Intent: define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. It is one of the behavioral patterns in the Gang of Four (GoF) book, where it is also called Policy.
Suppose a checkout calculates shipping cost. Standard shipping is a flat fee, express costs more per kilogram, and pickup in a store is free. A first version puts all three formulas in one method with an if/else chain on the shipping type. Each new option, such as a partner courier, means editing that method again, and testing one formula means going through all the others.
With Strategy, each formula becomes its own object or function with the same signature. The checkout receives the chosen strategy and calls it. Adding a courier means adding one new strategy; the checkout does not change.
Structure and participants
| Role | Responsibility |
|---|---|
| Strategy | Common interface for all versions of the algorithm |
| Concrete strategy | One implementation of the algorithm |
| Context | Holds a strategy, calls it when it needs the algorithm, and may let clients replace it |
| Client | Chooses a concrete strategy and gives it to the context |
TypeScript implementation
Because functions are values in TypeScript, the simplest strategy interface is a function type. The context is the Checkout class.
type Parcel = { weightKg: number; subtotal: number };
type ShippingStrategy = (parcel: Parcel) => number;
const standard: ShippingStrategy = (p) => (p.subtotal >= 50 ? 0 : 4.99);
const express: ShippingStrategy = (p) => 9.99 + 1.5 * p.weightKg;
const pickup: ShippingStrategy = () => 0;
class Checkout {
constructor(private shipping: ShippingStrategy) {}
setShipping(strategy: ShippingStrategy) {
this.shipping = strategy;
}
total(parcel: Parcel): number {
return Math.round((parcel.subtotal + this.shipping(parcel)) * 100) / 100;
}
}
const parcel = { weightKg: 2, subtotal: 30 };
const checkout = new Checkout(standard);
console.log(checkout.total(parcel)); // 34.99
checkout.setShipping(express);
console.log(checkout.total(parcel)); // 42.99
checkout.setShipping(pickup);
console.log(checkout.total(parcel)); // 30If a strategy needs configuration or several methods, use an interface and classes instead. This version also gives each strategy a name that the UI can display:
interface Shipping {
readonly label: string;
cost(weightKg: number): number;
}
class Courier implements Shipping {
readonly label: string;
constructor(private ratePerKg: number, company: string) {
this.label = company + " courier";
}
cost(weightKg: number) { return this.ratePerKg * weightKg; }
}
const options: Shipping[] = [new Courier(2, "North"), new Courier(3.5, "Fast")];
for (const o of options) console.log(o.label, o.cost(4));
// North courier 8
// Fast courier 14Before and after
Before, the algorithm choice is hard-coded inside the method:
function shippingCost(type: string, p: Parcel): number {
if (type === "standard") return p.subtotal >= 50 ? 0 : 4.99;
else if (type === "express") return 9.99 + 1.5 * p.weightKg;
else if (type === "pickup") return 0;
throw new Error("Unknown shipping type " + type);
}
// After: a lookup table of strategies replaces the chain
const strategies: Record<string, ShippingStrategy> = { standard, express, pickup };This fragment reuses the types and functions from the first example. The table makes the set of options data instead of code, so it can be built from configuration or extended by plugins.
When to use it and when not to
- Use it when there are several ways to do the same task and the choice depends on configuration, user input or the data.
- Use it to remove large conditional blocks that select between algorithms.
- Use it to test each algorithm in isolation and to let other code add new algorithms without editing the context.
- Do not use it when there is only one algorithm and no real need to change it. An extra interface adds indirection for nothing.
- Do not create classes when a function parameter is enough. In TypeScript, passing a function is the lightweight form of Strategy.
Real-world uses
Array.prototype.sort(compareFn)takes the comparison strategy as a function, so the same sort can order numbers, dates or names.- Java's
Comparatorinterface is a strategy forCollections.sortandList.sort. - Passport.js, the authentication middleware for Node.js, calls each login method (local password, OAuth providers) a strategy and lets applications plug them in.
- Compression and hashing libraries let callers choose an algorithm through a common interface.
Related patterns
| Pattern | How it differs from Strategy |
|---|---|
| State | Same structure, but states switch themselves as the object changes; a strategy is usually chosen by the client and stays until replaced. |
| Template Method | Template Method varies parts of an algorithm through inheritance and fixes the overall steps; Strategy replaces the whole algorithm through composition. |
| Command | A command is a request to run later; a strategy is a way of performing a task that the context calls when needed. |
| Decorator | A decorator changes an object's behavior by wrapping it from the outside; a strategy changes behavior from the inside by replacing one part. |
FAQ
What is the difference between Strategy and State?
The structures are almost identical. A strategy is picked by the client to choose how a task is done, while state objects change the context's current state themselves as a result of its actions.
Is passing a callback the Strategy pattern?
Yes, in languages with first-class functions a callback that implements a variable part of an algorithm, such as a comparator, is the Strategy pattern in its simplest form.
What is the difference between Strategy and Template Method?
Template Method uses inheritance: a base class fixes the steps and subclasses fill in some of them. Strategy uses composition: the context holds an object or function that it can replace at runtime.
Does Strategy follow the open/closed principle?
Yes. You can add new strategies without changing the context, so the context is open for extension but closed for modification.