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 dynamically

Interactive Strategy Pattern SimulatorLIVE PLAYGROUND

Interact with the controls below to see how this design pattern executes in memory at runtime.

Category: Behavioral
1. Select Interchangeable Strategy:
RUNTIME CONTEXT & CONSOLE OUTPUTActive: UPI
// Context delegates dynamically:
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

ParticipantRole & ResponsibilityType
PaymentStrategy (Interface)Declares pay(amount)interface
CreditCard, UPI, PayPalEncapsulate specific payment algorithmsclass
CheckoutContextHolds 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.