Home/Learn/Low Level Design/Strategy Pattern

Strategy Pattern

Beginner
Behavioral Patterns

Defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime without changing the client.

Overview

Strategy eliminates if-else or switch chains by encapsulating each algorithm variant behind a common interface. The Context class holds a reference to a Strategy interface and delegates the algorithm to the current strategy. Strategies are interchangeable — the Context does not know which concrete strategy it is using. This follows the Open/Closed Principle: adding a new algorithm requires only a new Strategy class, not modifying the Context. Java lambda expressions make single-method Strategy interfaces trivially implementable without explicit classes.

Strategy Implementation (Replace if-else)

Replace a growing if-else chain for payment processing with a Strategy interface. Each payment method becomes a ConcreteStrategy. The PaymentContext delegates to whichever strategy is currently set.

Java — Strategy (Payment Methods)
// Strategy interface
public interface PaymentStrategy {
    boolean pay(double amount);
    String methodName();
}

// Concrete Strategies
public class CreditCardStrategy implements PaymentStrategy {
    private final String cardNumber;
    private final String cvv;

    public CreditCardStrategy(String cardNumber, String cvv) {
        this.cardNumber = cardNumber;
        this.cvv = cvv;
    }

    @Override
    public boolean pay(double amount) {
        System.out.printf("Paid ₹%.2f via Credit Card ending %s%n",
            amount, cardNumber.substring(cardNumber.length() - 4));
        return true;
    }

    @Override public String methodName() { return "CREDIT_CARD"; }
}

public class UpiStrategy implements PaymentStrategy {
    private final String upiId;
    public UpiStrategy(String upiId) { this.upiId = upiId; }

    @Override
    public boolean pay(double amount) {
        System.out.printf("Paid ₹%.2f via UPI ID: %s%n", amount, upiId);
        return true;
    }

    @Override public String methodName() { return "UPI"; }
}

public class WalletStrategy implements PaymentStrategy {
    private double balance;
    public WalletStrategy(double balance) { this.balance = balance; }

    @Override
    public boolean pay(double amount) {
        if (balance < amount) {
            System.out.println("Insufficient wallet balance");
            return false;
        }
        balance -= amount;
        System.out.printf("Paid ₹%.2f via Wallet. Remaining: ₹%.2f%n", amount, balance);
        return true;
    }

    @Override public String methodName() { return "WALLET"; }
}

// Context
public class CheckoutContext {
    private PaymentStrategy strategy;

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

    public boolean checkout(double amount) {
        if (strategy == null) throw new IllegalStateException("No payment strategy set");
        System.out.println("Attempting payment via: " + strategy.methodName());
        return strategy.pay(amount);
    }
}

// Usage — strategy swapped at runtime
CheckoutContext ctx = new CheckoutContext();

ctx.setStrategy(new UpiStrategy("akshay@okaxis"));
ctx.checkout(999.0);

ctx.setStrategy(new WalletStrategy(500.0));
ctx.checkout(999.0); // fails — insufficient balance

ctx.setStrategy(new CreditCardStrategy("4111111111111234", "123"));
ctx.checkout(999.0); // succeeds

Strategy with Java Lambdas

When the Strategy interface has a single abstract method (@FunctionalInterface), Java lambdas replace concrete strategy classes. This dramatically reduces boilerplate for simple strategies.

Java — Strategy with lambdas and Comparator
@FunctionalInterface
public interface SortStrategy {
    void sort(int[] array);
}

public class Sorter {
    private SortStrategy strategy;
    public Sorter(SortStrategy strategy) { this.strategy = strategy; }
    public void sort(int[] array)        { strategy.sort(array); }
}

// Lambda strategies — no concrete class needed
Sorter bubbleSorter = new Sorter(array -> {
    // bubble sort implementation
    for (int i = 0; i < array.length - 1; i++)
        for (int j = 0; j < array.length - 1 - i; j++)
            if (array[j] > array[j+1]) { int t = array[j]; array[j] = array[j+1]; array[j+1] = t; }
});

// Method references as strategies
Sorter javaSorter = new Sorter(Arrays::sort); // java.util.Arrays.sort as strategy

// Java Comparator IS a Strategy (functional interface for sort comparison)
List<Course> courses = getCourses();
courses.sort(Comparator.comparing(Course::getRating).reversed()
    .thenComparing(Course::getTitle)); // composed strategies

Key Points to Remember

  • 1Strategy replaces conditional branches with polymorphism — each branch becomes a class.
  • 2The Context delegates to the Strategy — it does not know or care which strategy is active.
  • 3Prefer @FunctionalInterface strategies; pass lambdas instead of creating concrete classes.
  • 4Java Comparator is the canonical Strategy — Comparator.comparing() composes strategies.
  • 5Strategy (behavioral) vs Bridge (structural): Strategy swaps algorithms; Bridge separates abstraction from implementation.

Interview Questions

Sign in to ask Aria
1

How does Strategy pattern replace large if-else chains?

EasyAmazon
2

What is the relationship between Strategy pattern and Java Comparator?

MediumGoogle
3

How does Strategy differ from the Template Method pattern?

MediumMicrosoft
4

Can you implement a discount calculator using Strategy? Show the code.

MediumFlipkart
5

How would you select a Strategy at runtime based on user input?

EasyUber

Ask Aria about Strategy Pattern

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…