The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime. Instead of hardcoding behavior into a single class with conditionals, you extract each algorithm into its own class and let the client choose which one to use.
This is one of the most practical and frequently used design patterns in real-world software. If you have ever swapped out a payment method at checkout or changed a sorting algorithm based on data size, you have seen the Strategy Pattern in action.
Imagine you are building a payment processing system. Customers can pay with credit cards, PayPal, or cryptocurrency. The naive approach is to write a giant if-else or switch block inside a single method:
“If credit card, do X. If PayPal, do Y. If crypto, do Z.”
This works initially, but every time you add a new payment method (Apple Pay, bank transfer, etc.), you must modify the existing class. This violates the Open/Closed Principle, increases the risk of bugs, and makes the code harder to test because every payment path lives in the same method.
The Strategy Pattern solves this by extracting each payment algorithm into its own class that implements a common interface. A context class (the checkout processor) holds a reference to the current strategy and delegates the payment call to it. Adding a new payment method means creating a new class — no existing code changes required.
The context does not care how the payment is processed. It only knows that the strategy it holds can pay(). This decouples the “what” from the “how.”
Program to an interface, not an implementation. By depending on an abstraction (the strategy interface) rather than concrete payment classes, the system becomes flexible, testable, and open for extension without modification.
Scenario: A checkout system that supports multiple payment strategies.
// Strategy interface
public interface PaymentStrategy {
void pay(double amount);
String getPaymentMethod();
}
// Concrete Strategy: Credit Card
public class CreditCardPayment implements PaymentStrategy {
private final String cardNumber;
private final String cardHolder;
public CreditCardPayment(String cardNumber, String cardHolder) {
this.cardNumber = cardNumber;
this.cardHolder = cardHolder;
}
@Override
public void pay(double amount) {
String masked = "****-" + cardNumber.substring(cardNumber.length() - 4);
System.out.printf("Charged $%.2f to credit card %s (%s)%n",
amount, masked, cardHolder);
}
@Override
public String getPaymentMethod() {
return "Credit Card";
}
}
// Concrete Strategy: PayPal
public class PayPalPayment implements PaymentStrategy {
private final String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(double amount) {
System.out.printf("Transferred $%.2f via PayPal to %s%n", amount, email);
}
@Override
public String getPaymentMethod() {
return "PayPal";
}
}
// Concrete Strategy: Cryptocurrency
public class CryptoPayment implements PaymentStrategy {
private final String walletAddress;
public CryptoPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(double amount) {
System.out.printf("Sent $%.2f in crypto to wallet %s%n",
amount, walletAddress);
}
@Override
public String getPaymentMethod() {
return "Cryptocurrency";
}
}
// Context
public class CheckoutProcessor {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(double amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("Payment strategy not set");
}
System.out.println("Processing checkout...");
System.out.println("Payment method: " + paymentStrategy.getPaymentMethod());
paymentStrategy.pay(amount);
System.out.println("Checkout complete.\n");
}
}
// Usage
public class Main {
public static void main(String[] args) {
CheckoutProcessor processor = new CheckoutProcessor();
// Pay with credit card
processor.setPaymentStrategy(
new CreditCardPayment("4111111111111234", "John Doe"));
processor.checkout(99.99);
// Switch to PayPal at runtime
processor.setPaymentStrategy(
new PayPalPayment("john@example.com"));
processor.checkout(49.50);
// Switch to crypto
processor.setPaymentStrategy(
new CryptoPayment("0xABC123DEF456"));
processor.checkout(250.00);
}
}
Same scenario in Python, using abstract base classes and duck typing.
from abc import ABC, abstractmethod
# Strategy interface
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> None:
pass
@abstractmethod
def get_payment_method(self) -> str:
pass
# Concrete Strategy: Credit Card
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number: str, card_holder: str):
self.card_number = card_number
self.card_holder = card_holder
def pay(self, amount: float) -> None:
masked = f"****-{self.card_number[-4:]}"
print(f"Charged ${amount:.2f} to credit card {masked} ({self.card_holder})")
def get_payment_method(self) -> str:
return "Credit Card"
# Concrete Strategy: PayPal
class PayPalPayment(PaymentStrategy):
def __init__(self, email: str):
self.email = email
def pay(self, amount: float) -> None:
print(f"Transferred ${amount:.2f} via PayPal to {self.email}")
def get_payment_method(self) -> str:
return "PayPal"
# Concrete Strategy: Cryptocurrency
class CryptoPayment(PaymentStrategy):
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def pay(self, amount: float) -> None:
print(f"Sent ${amount:.2f} in crypto to wallet {self.wallet_address}")
def get_payment_method(self) -> str:
return "Cryptocurrency"
# Context
class CheckoutProcessor:
def __init__(self):
self._payment_strategy: PaymentStrategy | None = None
def set_payment_strategy(self, strategy: PaymentStrategy) -> None:
self._payment_strategy = strategy
def checkout(self, amount: float) -> None:
if self._payment_strategy is None:
raise RuntimeError("Payment strategy not set")
print("Processing checkout...")
print(f"Payment method: {self._payment_strategy.get_payment_method()}")
self._payment_strategy.pay(amount)
print("Checkout complete.\n")
# Usage
if __name__ == "__main__":
processor = CheckoutProcessor()
# Pay with credit card
processor.set_payment_strategy(
CreditCardPayment("4111111111111234", "John Doe"))
processor.checkout(99.99)
# Switch to PayPal at runtime
processor.set_payment_strategy(
PayPalPayment("john@example.com"))
processor.checkout(49.50)
# Switch to crypto
processor.set_payment_strategy(
CryptoPayment("0xABC123DEF456"))
processor.checkout(250.00)
if-else or switch blocks that choose between behaviors based on type or configuration.Comparator — Collections.sort(list, comparator) lets you pass different sorting strategies.AuthenticationProvider — Different authentication strategies (LDAP, OAuth, database) implement the same interface.sorted() with key — The key parameter is a strategy function that determines sort order.