Skip to content
SP StackPractices
beginner

Strategy Pattern

Define a family of algorithms, encapsulate each one, and make them interchangeable. A behavioral design pattern for flexible behavior selection.

Topics: design

Strategy Pattern

Overview

The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one as a separate class, and makes them interchangeable at runtime. It lets the algorithm vary independently from the clients that use it.

It is ideal when you need to select from multiple ways to perform an operation, such as different sorting strategies, payment methods, or compression algorithms.

When to Use

Use the Strategy Pattern when:

  • You have multiple ways to perform an operation and want to switch between them at runtime
  • A class has a large conditional (if/switch) for selecting behavior
  • You want to isolate algorithm implementation details from the context that uses them
  • You need to add new algorithms without modifying existing code (Open/Closed Principle)
  • Different clients need different variants of the same algorithm

Solution

Python

from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float) -> str:
        pass

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount: float) -> str:
        return f"Paid ${amount} with Credit Card"

class PayPalPayment(PaymentStrategy):
    def pay(self, amount: float) -> str:
        return f"Paid ${amount} via PayPal"

class Checkout:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def process(self, amount: float):
        return self.strategy.pay(amount)

# Usage
checkout = Checkout(CreditCardPayment())
print(checkout.process(100.0))
checkout.strategy = PayPalPayment()
print(checkout.process(50.0))

JavaScript

class CreditCardPayment {
  pay(amount) {
    return `Paid $${amount} with Credit Card`;
  }
}

class PayPalPayment {
  pay(amount) {
    return `Paid $${amount} via PayPal`;
  }
}

class Checkout {
  constructor(strategy) {
    this.strategy = strategy;
  }

  process(amount) {
    return this.strategy.pay(amount);
  }
}

// Usage
const checkout = new Checkout(new CreditCardPayment());
console.log(checkout.process(100));
checkout.strategy = new PayPalPayment();
console.log(checkout.process(50));

Java

interface PaymentStrategy {
    String pay(double amount);
}

class CreditCardPayment implements PaymentStrategy {
    public String pay(double amount) {
        return "Paid $" + amount + " with Credit Card";
    }
}

class PayPalPayment implements PaymentStrategy {
    public String pay(double amount) {
        return "Paid $" + amount + " via PayPal";
    }
}

class Checkout {
    private PaymentStrategy strategy;

    Checkout(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    String process(double amount) {
        return strategy.pay(amount);
    }

    void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
}

// Usage
Checkout checkout = new Checkout(new CreditCardPayment());
System.out.println(checkout.process(100));
checkout.setStrategy(new PayPalPayment());
System.out.println(checkout.process(50));

Explanation

The Strategy Pattern separates behavior into three roles:

  • Strategy Interface (PaymentStrategy): Defines the contract all algorithms must follow
  • Concrete Strategies (CreditCardPayment, PayPalPayment): The actual interchangeable algorithms
  • Context (Checkout): Uses a strategy through the interface, unaware of the concrete implementation

This eliminates large conditional blocks and makes adding new strategies as simple as creating a new class.

Variants

VariantUse CaseTrade-off
Simple StrategySingle context, few strategiesEasy to start, direct coupling
Strategy with FactoryDynamic strategy selectionMore flexible, adds indirection
Functional StrategyLanguages with first-class functionsConcise, but less explicit than classes

Best Practices

  • Use an interface or abstract base to ensure all strategies have the same signature
  • Keep strategies stateless when possible for easier reuse and testing
  • Inject the strategy via constructor or setter rather than hardcoding it
  • Document strategy differences so callers know which to choose
  • Avoid strategy bloat: if you have dozens of strategies, consider a different abstraction

Common Mistakes

  • Leaking context internals: Strategies should not depend on private details of the context
  • Overuse for trivial cases: A simple if statement does not always need a Strategy class
  • Tight coupling: The context knowing about concrete strategy classes instead of the interface
  • Inconsistent interfaces: Strategies with different method signatures break interchangeability
  • State management: Strategies holding mutable state can cause unexpected side effects

Frequently Asked Questions

Q: What is the difference between Strategy and State patterns? A: Strategy is about interchangeable algorithms chosen by the client. State is about changing behavior based on the object’s internal state transitions.

Q: Can I use functions instead of classes for strategies? A: Yes, in languages with first-class functions (JavaScript, Python, Go) you can pass functions directly. Classes are better when strategies need configuration or multiple methods.

Q: How many strategies is too many? A: There is no hard limit, but if you find yourself with dozens, consider grouping them by category or using a registry/factory pattern.