Definition
- Strategy pattern – defines a family of interchangeable algorithms behind a common interface and lets the algorithm be selected at runtime.
- Context – holds a reference to the strategy interface and delegates to whichever concrete strategy it was given, without knowing which one.
- Open/Closed – adding a new algorithm means adding a new strategy class rather than editing existing code.
- Functional fit – in Java, a single-method strategy interface is a
@FunctionalInterface, so a lambda or method reference can replace a named class.
When Rajesh pays at the National Bank of India, he can choose UPI, card, or net banking. The payment counter does not care which he picks; it just runs whatever payment method he hands it. Each payment method is a self-contained strategy, and adding a new one like a wallet means opening a new method lane without touching the existing lanes. The counter stays the same; only the chosen method changes.
Understanding the Concepts
What is the Strategy pattern?
Strategy has three parts: a strategy interface that declares the algorithm, concrete strategies that implement it differently, and a context that holds a strategy and delegates to it. The context depends only on the interface, so you can swap strategies without changing the context.
package com.bank.service;
import java.math.BigDecimal;
// Strategy interface
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// Concrete strategies
public class UpiPayment implements PaymentStrategy {
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " via UPI");
}
}
public class CardPayment implements PaymentStrategy {
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " via Card");
}
}
public class NetBankingPayment implements PaymentStrategy {
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " via Net Banking");
}
}
// Context
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void checkout(BigDecimal amount) {
strategy.pay(amount);
}
}
How does Strategy enforce the Open/Closed Principle?
Open for extension means you can add a new payment method. Closed for modification means you do not touch the tested code to do it. Without Strategy, a payment method would be a growing switch.
package com.bank.service;
import java.math.BigDecimal;
// VIOLATION of OCP: every new method edits this switch
public class PaymentProcessorBad {
public void pay(String type, BigDecimal amount) {
if ("UPI".equals(type)) {
System.out.println("Paid via UPI: " + amount);
} else if ("CARD".equals(type)) {
System.out.println("Paid via Card: " + amount);
}
// adding WALLET means editing here
}
}
With Strategy, adding WalletPayment is a new class that implements PaymentStrategy. The context and existing strategies are never touched.
// New strategy – zero edits to existing code
public class WalletPayment implements PaymentStrategy {
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " via Wallet");
}
}
Where does Strategy appear in the Java standard library?
Strategy is everywhere in the JDK:
Comparatoris the clearest example.list.sort(Comparator.comparing(Account::getBalance))plugs a comparison algorithm into a fixed sort.RejectedExecutionHandlerinThreadPoolExecutoris a strategy for what to do when the queue is full.Collectorin the Streams API is a strategy for how to accumulate stream elements.RunnableandCallablepassed to a thread or executor are strategies for what work to do.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class StrategyInJdk {
record Account(String name, BigDecimal balance) {}
public static void main(String[] args) {
List<Account> accounts = Arrays.asList(
new Account("Rajesh", new BigDecimal("150000")),
new Account("Priya", new BigDecimal("250000")),
new Account("Pankaj", new BigDecimal("50000"))
);
// Comparator is a Strategy
accounts.sort(Comparator.comparing(Account::balance));
accounts.forEach(a -> System.out.println(a.name() + ": " + a.balance()));
}
}
Pankaj: 50000
Rajesh: 150000
Priya: 250000
How do you implement Strategy with lambdas in modern Java?
A single-method interface is a @FunctionalInterface, so a strategy can be a lambda or method reference instead of a named class.
package com.bank.demo;
import java.math.BigDecimal;
public class StrategyLambdaDemo {
@FunctionalInterface
interface InterestStrategy {
BigDecimal calculate(BigDecimal balance);
}
public static void main(String[] args) {
// Strategies as lambdas – no named class needed
InterestStrategy savings = balance -> balance.multiply(new BigDecimal("0.04"));
InterestStrategy current = balance -> balance.multiply(new BigDecimal("0.02"));
InterestStrategy nre = balance -> balance.multiply(new BigDecimal("0.065"));
BigDecimal balance = new BigDecimal("500000");
System.out.println("Savings interest: " + savings.calculate(balance));
System.out.println("Current interest: " + current.calculate(balance));
System.out.println("NRE interest: " + nre.calculate(balance));
}
}
Savings interest: 20000.000
Current interest: 10000.000
NRE interest: 32500.000
How does Strategy work with Spring injection?
In Spring, injecting a Map gives runtime selection by bean name for free.
package com.bank.service;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Map;
@Component
public class PaymentService {
private final Map<String, PaymentStrategy> strategies;
// Spring injects all PaymentStrategy beans, keyed by bean name
public PaymentService(Map<String, PaymentStrategy> strategies) {
this.strategies = strategies;
}
public void pay(String method, BigDecimal amount) {
PaymentStrategy strategy = strategies.get(method);
if (strategy == null) {
throw new IllegalArgumentException("Unknown payment method: " + method);
}
strategy.pay(amount);
}
}
Adding a new payment method is literally adding a new @Component bean. No factory edit, no switch statement.
How is Strategy different from Template Method?
Strategy selects the entire algorithm at runtime via composition. Template Method defines a skeleton in a base class and lets subclasses override specific steps via inheritance.
| Aspect | Strategy | Template Method |
|---|---|---|
| Mechanism | Composition (has-a) | Inheritance (is-a) |
| Selection | Runtime, swap freely | Compile time, fixed by subclass |
| Granularity | Whole algorithm | Individual steps |
| Coupling | Loose | Tight (subclass to parent) |
// Strategy: compose at runtime
PaymentContext ctx = new PaymentContext(new UpiPayment());
ctx.checkout(new BigDecimal("5000"));
ctx.setStrategy(new CardPayment()); // swap at runtime
ctx.checkout(new BigDecimal("3000"));
// Template Method: fixed by subclass at compile time
public abstract class LoanProcessor {
public final void process() {
validate();
calculateEmi(); // subclass provides this step
disburse();
}
protected abstract void calculateEmi();
// ...
}
What are common mistakes with Strategy?
- Using Strategy for a single, fixed algorithm that will never vary. A plain method is simpler. - Creating a strategy hierarchy when a simple lambda suffices. In modern Java, `amount -> amount.multiply(rate)` beats a named class for trivial logic. - Forgetting null checks on the strategy reference, which causes `NullPointerException` at runtime.
What should a senior engineer know?
- Strategy is the pattern Rajesh reaches for most often because it directly enforces OCP.
- In a banking system, strategies absorb regulatory changes: new interest rules, new fee types, new payment methods.
- The
Mappattern in Spring replaces the classic Factory plus Strategy combination. - Strategy works best when the number of algorithms is open-ended. For two fixed variants, a simple if-else is simpler and honest.
Visual Overview
Strategy Pattern Structure
──────────────────────────
┌──────────────────────┐
│ PaymentContext │
│ ───────────────── │
│ - strategy: Strategy │
│ + checkout(amount) │──── delegates ────▶ strategy.pay(amount)
└──────────────────────┘
│
│ holds reference to
▼
┌──────────────────────┐
│ PaymentStrategy │ ◄── interface
│ + pay(amount) │
└──────────┬───────────┘
│
┌───────┼────────┬──────────┐
▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ UPI │ │ Card │ │ Net │ │Wallet│
│ │ │ │ │ Bank │ │ (new)│
└──────┘ └──────┘ └──────┘ └──────┘
Adding Wallet = new class only
Context + existing strategies: UNTOUCHED
Key Takeaway
The Strategy pattern defines a family of interchangeable algorithms behind a common interface and lets Rajesh choose the algorithm at runtime. It has three parts: a strategy interface that declares the algorithm, concrete strategies that implement it differently, and a context that holds a strategy and delegates to it without knowing which concrete one it has. The whole point is that the context depends only on the interface, so algorithms swap freely.
It appears all over the Java standard library. The clearest example is Comparator, where Rajesh plugs a comparison algorithm into a fixed sort. The thread pool's RejectedExecutionHandler is a strategy for what to do when the queue is full, the Collector in the Streams API is a strategy, and even a Runnable handed to a thread is a pluggable unit of behaviour.
Strategy enforces the Open/Closed Principle directly. At the National Bank of India, Pankaj once broke the card payment branch while adding net banking to a growing switch statement, which caused failed card payments in production. With Strategy, each payment method is its own class implementing PaymentStrategy, the checkout context just looks it up and delegates, and adding a wallet method is a new class registered in a map with zero edits to the context or the existing strategies.
In modern Java, a Strategy is often just a lambda because a single-method interface is a functional interface, so Rajesh can write an interest strategy as a one-line lambda. In Spring, injecting a Map gives runtime selection for free, so adding a payment method is literally adding a new bean.
Remember one sentence: Strategy makes algorithms interchangeable behind one interface, enforcing OCP because adding a new algorithm is a new class, never an edit.
---