Strategy means: put each version of an algorithm in its own small class, all shaped the same way, and let the calling code pick — or swap — which one runs.
You do not write one method with a wall of if/else branches. Each “how to do it” becomes its own class. They all answer the same question, so the rest of your program does not care which one it is talking to.
Three pieces work together:
A strategy interface — the one behavior every algorithm must provide.
Several concrete strategies — each class implements that interface its own way.
A context — holds some strategy and calls it, without knowing which one.
The problem it solves
Without Strategy, picking an algorithm tends to live inside one method as a growing chain of conditionals.
The context does not know if it is holding a StandardShipping or an ExpressShipping. It only knows it has some strategy, and that is enough.
class ShippingCalculator: def __init__(self, strategy: ShippingStrategy) -> None: self.strategy = strategy def calculate(self, order) -> float: return self.strategy.calculate_cost(order)# usecalculator = ShippingCalculator(StandardShipping())calculator.calculate(order)calculator.strategy = ExpressShipping()calculator.calculate(order) # same call site, different math
public class ShippingCalculator { private ShippingStrategy strategy; public ShippingCalculator(ShippingStrategy strategy) { this.strategy = strategy; } public void setStrategy(ShippingStrategy strategy) { this.strategy = strategy; } public double calculate(Order order) { return strategy.calculateCost(order); }}// useShippingCalculator calculator = new ShippingCalculator(new StandardShipping());calculator.calculate(order);calculator.setStrategy(new ExpressShipping());calculator.calculate(order); // same call site, different math
type Calculator struct { strategy Strategy}func NewCalculator(strategy Strategy) *Calculator { return &Calculator{strategy: strategy}}func (c *Calculator) SetStrategy(strategy Strategy) { c.strategy = strategy}func (c *Calculator) Calculate(order Order) float64 { return c.strategy.CalculateCost(order)}// usecalculator := NewCalculator(Standard{})calculator.Calculate(order)calculator.SetStrategy(Express{})calculator.Calculate(order) // same call site, different math
class ShippingCalculator { constructor(private strategy: ShippingStrategy) {} setStrategy(strategy: ShippingStrategy) { this.strategy = strategy; } calculate(order: Order) { return this.strategy.calculateCost(order); }}// useconst calculator = new ShippingCalculator(new StandardShipping());calculator.calculate(order);calculator.setStrategy(new ExpressShipping());calculator.calculate(order); // same call site, different math
Picking a strategy without reintroducing if/else
Strategy removes the branching inside the algorithm. Something still has to decide which strategy to use — if that decision is also if/else, you have only moved the problem. A lookup map keeps the decision in one place instead.
const strategies: Record<string, ShippingStrategy> = { STANDARD: new StandardShipping(), EXPRESS: new ExpressShipping(), FREE: new FreeShipping(),};function getStrategy(shippingType: string): ShippingStrategy { const strategy = strategies[shippingType]; if (!strategy) { throw new Error(`Unknown shipping type: ${shippingType}`); } return strategy;}
Adding a fourth shipping option now means adding one class and one map entry. The calculator and every existing strategy stay untouched.
Strategy without a class
If a strategy is a single operation with no state of its own, you often do not need a named class at all — a plain function works, since Strategy only needs “something callable with the same shape.”
This is still Strategy — an interface-shaped thing, several implementations, a context that delegates — just without a class per algorithm. Reach for a full class once a strategy needs its own fields or several methods.
Worked example: customer discount engine
A pricing engine that applies a different discount depending on a customer’s membership tier.
If a “Platinum” tier appears next quarter, you write one new discount class. PricingEngine never changes, and neither does any existing discount class.
When it helps
Use Strategy whenever a task can be done in more than one way, and the choice of which way needs to change independently of the code that requests the result — sorting, payment methods, pricing rules, validation rules, compression formats.
It pays off anywhere you would otherwise write a long if/else or switch chain over a type, where each branch is really its own self-contained piece of logic.
When it hurts
If there is genuinely only one way to do the task, or the algorithm is a one-line calculation, Strategy adds an interface and a class for no real benefit. A private helper function reads better.
Strategy also does not remove decision-making by itself — it only relocates it. If you replace an if/else chain inside an algorithm with an if/else chain that picks which strategy to construct, you kept the exact problem you were trying to avoid. That is why a map, factory, or dependency injection usually accompanies Strategy.
Practical tips
Define the strategy as an interface, or a plain function type if it is a single operation with no state.
Keep strategies stateless when you can — a strategy that is a pure function is easy to reuse, test, and share.
Let a map or factory choose the concrete strategy. Do not let the caller construct the concrete class directly.
Document what distinguishes each strategy (cost, speed, accuracy) so a future reader knows why more than one exists.
Check your understanding
Strategy in one line?
A family of interchangeable algorithms behind one interface.
What problem does it remove?
Long if/else or switch chains where each branch is really a distinct algorithm.
Does Strategy remove the need to choose an algorithm?
No — it moves that choice into a separate place, often a map or factory.
How is Strategy different from Factory?
Factory creates the right object. Strategy chooses the right algorithm for a task the context runs.
When should you skip it?
When there is only one way to do the task, or the algorithm is trivial.
What to learn next
Next: Observer Pattern — how one object can automatically notify many others when something changes.