Summary
The Strategy Pattern defines a set of interchangeable behaviors (strategies) behind a shared interface. The consuming class delegates to whichever strategy is injected, enabling behavior to vary without modifying the class itself. It is the behavioral realization of the Open/Closed Principle.
Problem
Conditional logic that selects between multiple algorithms or behaviors tends to grow. Each new case requires modifying existing code, increasing risk and complexity.
- How do you add a new payment processor without touching the checkout flow?
- How do you support multiple sorting algorithms without a chain of
if/else? - How do you allow runtime selection of behavior without open-ended switch statements?
Solution
Extract each algorithm or behavior variant into its own class that implements a shared interface. The context class holds a reference to the interface and delegates to it.
interface ShippingStrategy {
calculateCost(order: Order): number;
}
class StandardShipping implements ShippingStrategy {
calculateCost(order: Order) { return order.weight * 2.5; }
}
class ExpressShipping implements ShippingStrategy {
calculateCost(order: Order) { return order.weight * 5.0 + 10; }
}
class OrderService {
constructor(private shipping: ShippingStrategy) {}
getTotal(order: Order) {
return order.subtotal + this.shipping.calculateCost(order);
}
}
Strategies are swapped via constructor injection or a setter. The context code never changes when new strategies are added.
The same shape holds in Python, using Protocol for the interface and constructor injection for the swap:
from typing import Protocol
class ShippingStrategy(Protocol):
def calculate_cost(self, order: "Order") -> float: ...
class StandardShipping:
def calculate_cost(self, order: "Order") -> float:
return order.weight * 2.5
class ExpressShipping:
def calculate_cost(self, order: "Order") -> float:
return order.weight * 5.0 + 10
class OrderService:
def __init__(self, shipping: ShippingStrategy):
self._shipping = shipping
def get_total(self, order: "Order") -> float:
return order.subtotal + self._shipping.calculate_cost(order)
# Swap strategies without touching OrderService
standard_total = OrderService(StandardShipping()).get_total(order)
express_total = OrderService(ExpressShipping()).get_total(order)
Protocol gives structural typing — any class with a matching calculate_cost method satisfies ShippingStrategy, with no explicit inheritance required.
Live Playground
Experiment with the pattern below. The two tabs show a checkout flow before extraction, where shipping cost logic lives in an if/else chain inside OrderService, and the after version where each shipping method is its own ShippingStrategy implementation swapped in at runtime.
When to Use
- Multiple variants of an algorithm that need to be selected at runtime or configuration time
- Replacing conditional branching (
if/else,switch) that selects between implementations - When the Open/Closed Principle is important — adding behavior without modifying existing code
- Payment processors, sorting algorithms, export formats, notification channels
Avoid when:
- There is only one algorithm and no foreseeable variation — abstraction is premature
- The strategies share significant state or setup that makes extraction awkward
Trade-offs
| Benefit | Cost |
|---|---|
| New behaviors added without modifying existing code | More classes/files to manage |
| Each strategy is independently testable | Callers must be aware of available strategies |
| Eliminates conditional logic from context class | Can feel like over-engineering for simple cases |
| Strategies are composable (wrap one in another) | Interface must be stable — changing it touches all implementations |
Related Patterns
- Dependency Injection — The mechanism for delivering strategies to context classes
- Composition Over Inheritance — Strategy is a key pattern that favors composition
- Hexagonal Architecture — Ports are strategy interfaces applied at the architectural scale
- CQRS — command handlers can use the Strategy Pattern to select pluggable business rules per command type
- Single Responsibility — each strategy class has a single responsibility: implement one variant of the algorithm