Back to All Design Patterns
Pattern 9 of 22
Behavioral PatternBEGINNER7 min deep dive
Strategy Pattern Pattern
Defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime.
Real-World Mental Model / Metaphor:
Traveling to the airport. Depending on your budget and time, you can choose a strategy: taking the Subway, calling an Uber, or riding a Bicycle. The destination is the same, but the algorithm differs.
Interactive Runtime Simulator
Test and tweak inputs dynamicallyInteractive Strategy Pattern SimulatorLIVE PLAYGROUND
Interact with the controls below to see how this design pattern executes in memory at runtime.
1. Select Interchangeable Strategy:
RUNTIME CONTEXT & CONSOLE OUTPUTActive: UPI
// Context delegates dynamically:
cart.setPaymentStrategy(new UpiStrategy());
cart.checkout(250);
cart.setPaymentStrategy(new UpiStrategy());
cart.checkout(250);
>System initialized with default payment strategy: UPI FastPay
The Problem It Solves
Giant `switch` or `if-else if-else` statements checking `paymentType == 'CREDIT_CARD'` scattered throughout the codebase.
The Architectural Solution
Extract each algorithm into a separate strategy class that implements a common Strategy interface. The Context class delegates execution to its current strategy.
Pattern Structure & Participants
| Participant | Role & Responsibility | Type |
|---|---|---|
| PaymentStrategy (Interface) | Declares pay(amount) | interface |
| CreditCard, UPI, PayPal | Encapsulate specific payment algorithms | class |
| CheckoutContext | Holds a reference to current Strategy and calls pay() | class |
Collaboration Rules:
- Client sets strategy on context: context.setStrategy(new UpiStrategy())
- Context delegates execution: strategy.pay(amount)
Production Code Implementation
Strategy Pattern.java
// 1. Strategy Interface
public interface PaymentStrategy {
void pay(double amount);
String getName();
}
// 2. Concrete Strategies
public class CreditCardStrategy implements PaymentStrategy {
private final String cardNumber;
public CreditCardStrategy(String cardNumber) { this.cardNumber = cardNumber; }
@Override
public void pay(double amount) {
System.out.println("Charged $" + amount + " to Credit Card ending in " + cardNumber.substring(cardNumber.length() - 4));
}
public String getName() { return "Credit Card"; }
}
public class UpiStrategy implements PaymentStrategy {
private final String upiId;
public UpiStrategy(String upiId) { this.upiId = upiId; }
@Override
public void pay(double amount) {
System.out.println("Transferred $" + amount + " instantly via UPI VPA: " + upiId);
}
public String getName() { return "UPI FastPay"; }
}
// 3. Context
public class ShoppingCart {
private PaymentStrategy strategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void checkout(double totalAmount) {
if (strategy == null) throw new IllegalStateException("Payment strategy not selected!");
strategy.pay(totalAmount);
}
} When to Use:
- •When you need to use different variants of an algorithm within an object and want to switch between them at runtime.
- •When you have many related classes that differ only in the way they execute some behavior.
- •To isolate the business logic of a class from the implementation details of algorithms.
When to Avoid (Anti-Pattern):
- •If you only have a couple of algorithms and they rarely change, introducing new interfaces and classes adds unnecessary complexity.
Staff Engineering Interview Insights & Gotchas
- Show how Strategy eliminates messy `switch(type)` code and satisfies the Open/Closed Principle.
- Mention that modern Java and TypeScript can use Lambdas/Functional Interfaces as anonymous strategies.