design-pattern
Strategy Design Pattern
I had a checkout system with a growing if/else chain for payment methods. Credit card? One block. PayPal? Another. Apple Pay? A third. Bank transfer? A fo...
23 Mar 2024

I had a checkout system with a growing if/else chain for payment methods. Credit card? One block. PayPal? Another. Apple Pay? A third. Bank transfer? A fourth. Every new payment method meant adding another branch, and the function was already 200 lines long.
The Strategy pattern replaces that branching with interchangeable objects. You define a family of algorithms, encapsulate each one in its own class, and swap them at runtime. The calling code doesn't know which strategy it's using — it just calls pay().
Think of it like choosing how to get to work. You can drive, bike, or take the bus. The goal is the same (get to work), but the strategy is different. You pick the strategy based on the situation — weather, traffic, mood.
class PaymentStrategy {
pay(amount) {}
}
class CreditCardPayment extends PaymentStrategy {
pay(amount) {
console.log(`Paid $${amount} via Credit Card`);
}
}
class PayPalPayment extends PaymentStrategy {
pay(amount) {
console.log(`Paid $${amount} via PayPal`);
}
}
class BitcoinPayment extends PaymentStrategy {
pay(amount) {
console.log(`Paid $${amount} via Bitcoin`);
}
}
class PaymentContext {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
executePayment(amount) {
this.strategy.pay(amount);
}
}
const context = new PaymentContext(new CreditCardPayment());
context.executePayment(100); // Paid $100 via Credit Card
context.setStrategy(new PayPalPayment());
context.executePayment(50); // Paid $50 via PayPal
context.setStrategy(new BitcoinPayment());
context.executePayment(25); // Paid $25 via Bitcoin
PaymentContext is the context — it holds a strategy and delegates to it. The concrete strategies (CreditCardPayment, PayPalPayment, BitcoinPayment) each implement the same interface. The context doesn't know which one it's using. It doesn't care.
Adding Apple Pay? Write ApplePayPayment. No changes to PaymentContext or any existing strategy.
In JavaScript, Functions Are Strategies
You don't always need classes. In JavaScript, you can use plain functions:
const strategies = {
creditCard: (amount) => console.log(`Paid $${amount} via Credit Card`),
paypal: (amount) => console.log(`Paid $${amount} via PayPal`),
bitcoin: (amount) => console.log(`Paid $${amount} via Bitcoin`),
};
function pay(method, amount) {
strategies[method](amount);
}
pay("paypal", 75); // Paid $75 via PayPal
Same pattern, less ceremony. Use classes when strategies have internal state or complex logic. Use functions when they're simple transformations.
The benefit: Open/Closed Principle. New strategies don't modify existing code. The context and strategies are independently testable. You can swap behavior at runtime based on user input, configuration, or A/B tests.
The cost: More classes or functions to manage. The client code must know which strategy to select — the decision logic has to live somewhere. And if you only have two or three strategies that will never change, a simple if/else is more readable.
I use Strategy for payment processing, sorting algorithms, validation rules, notification channels, and anywhere I see a switch statement that grows over time.