DevNotes
Support
Essential Design Patterns · Chapter 05

Strategy Pattern

Interchangeable algorithms chosen at runtime — the payment-strategy example, Context and Strategy roles, replacing if-else chains, lambdas as strategies, and selecting strategies with Spring injection.

◷ 5 min read§ 11 sections? 5 interview questions{ } 7 code examples

#Introduction

Purpose: define a family of algorithms, encapsulate each one, and choose one at runtime.

Instead of one class full of if/switch branches for every variant of a behaviour, each variant becomes its own class behind a common interface, and the context simply delegates to whichever strategy it's given.

#Example: Payment

The Strategy Interface and Concrete Strategies

Java
interface PaymentStrategy {
    void pay(double amount);
}

class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using Credit Card");
    }
}

class UpiPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using UPI");
    }
}

The Context

Java
class PaymentService {
    private PaymentStrategy strategy;

    public PaymentService(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

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

    public void checkout(double amount) {
        strategy.pay(amount);
    }
}

The Client

Java
public class Main {
    public static void main(String[] args) {
        PaymentService paymentService = new PaymentService(new UpiPayment());
        paymentService.checkout(1000);

        paymentService.setStrategy(new CreditCardPayment());
        paymentService.checkout(2000);
    }
}
Output
Paid 1000.0 using UPI
Paid 2000.0 using Credit Card

#Structure

  • PaymentServiceContext — holds a strategy and delegates to it
    • PaymentStrategyStrategy interface
      • UpiPaymentConcrete strategy A
      • CreditCardPaymentConcrete strategy B
RoleIn the exampleResponsibility
StrategyPaymentStrategyCommon interface for all algorithms
Concrete strategyUpiPayment, CreditCardPaymentOne algorithm each
ContextPaymentServiceHolds a strategy and delegates to it; doesn't know which one it has

#Replacing an if-else Chain

Java
// ❌ Every new payment method edits this method
public void checkout(String method, double amount) {
    if (method.equals("UPI")) {
        // UPI logic
    } else if (method.equals("CARD")) {
        // card logic
    } else if (method.equals("WALLET")) {
        // wallet logic
    }
}

With Strategy, adding a wallet means adding one WalletPayment class — PaymentService doesn't change (Open/Closed Principle), and each algorithm can be unit-tested on its own.

#Lambdas as Strategies

Because PaymentStrategy has a single abstract method, it's a functional interface — small strategies can be lambdas:

Java
PaymentService service = new PaymentService(amount -> System.out.println("Paid " + amount + " by wallet"));

The JDK is full of this: a Comparator passed to list.sort() is a strategy for ordering, and Predicate/Function arguments to streams are strategies too.

Java
employees.sort(Comparator.comparing(Employee::salary).reversed());   // sorting strategy

#Strategy in Spring

Spring makes selecting a strategy at runtime easy: inject all implementations and pick one.

Java
public interface PaymentStrategy {
    String method();                 // "UPI", "CARD", ...
    void pay(BigDecimal amount);
}

@Component class UpiPayment implements PaymentStrategy { ... }
@Component class CardPayment implements PaymentStrategy { ... }

@Service
public class PaymentService {

    private final Map<String, PaymentStrategy> strategies;

    public PaymentService(List<PaymentStrategy> all) {
        this.strategies = all.stream()
                .collect(Collectors.toMap(PaymentStrategy::method, Function.identity()));
    }

    public void checkout(String method, BigDecimal amount) {
        PaymentStrategy strategy = strategies.get(method);
        if (strategy == null) throw new IllegalArgumentException("Unsupported payment method: " + method);
        strategy.pay(amount);
    }
}

A new payment method is now just a new @Component — nothing else changes. Spring itself uses strategies widely: PasswordEncoder, ResourceLoader, TaskExecutor, and different AuthenticationProviders.

#Strategy vs Similar Patterns

PatternDifference
StateSame structure, but the object switches its own behaviour as its internal state changes; with Strategy the client chooses
Template MethodVaries steps through inheritance (subclasses override hooks); Strategy varies the whole algorithm through composition
FactoryDecides which object to create; often used together with Strategy to pick the strategy

#When to Use

  • Several variants of an algorithm or business rule (pricing, discounts, tax, payment, shipping cost, sorting).
  • The variant must be chosen at runtime or configured.
  • A method keeps growing if/switch branches on a "type".

#Interview Questions

Q1What is the Strategy pattern?

A behavioral pattern that encapsulates interchangeable algorithms behind a common interface and lets the behaviour be selected at runtime.

Q2What problem does it solve?

It replaces conditional logic that selects behaviour by type, so new behaviours can be added without modifying existing code, and each algorithm is isolated and testable.

Q3What's the difference between Strategy and State?

They're structurally similar. In Strategy the client chooses the algorithm; in State the object changes its own behaviour automatically when its internal state changes.

Q4Give a JDK example of Strategy.

Comparator passed to Collections.sort() or List.sort(); also the functional interfaces passed to stream operations.

Q5How would you implement Strategy in Spring?

Make each algorithm a @Component implementing one interface, inject List<Interface> or Map<String, Interface>, and choose the implementation by key at runtime.

#Common Mistakes

  • Still using if/switch inside the context to choose behaviour.
  • Strategies that depend on the context's internals — they should only need the interface's parameters.
  • A class per trivial variant when a lambda would do.
  • No handling for an unknown key when looking strategies up.

#Key Takeaways

  • Strategy encapsulates interchangeable algorithms behind one interface.
  • The context delegates; the algorithm can be swapped at runtime.
  • It eliminates growing conditional chains (Open/Closed Principle).
  • Functional interfaces and lambdas are lightweight strategies.
  • In Spring, inject all implementations and select by key.