Strategy pattern
The strategy pattern extracts a family of algorithms behind an interface so the client can swap behaviors at runtime without touching the context class.
The Problem It Solves
You're building a payment system. The checkout class needs to process credit cards, PayPal, and crypto. Without a pattern, you end up with a growing if/else chain that violates Open/Closed every time someone adds a new payment method.
Three methods today, seven next quarter. Each branch has completely different logic, different dependencies, different error handling. Testing one branch means loading the entire class. The mistake I see most often in interviews is candidates describing this exact structure and calling it "good enough."
Here is what changes when you apply the Strategy pattern.
Structure
CheckoutService is the context: it holds a reference to a PaymentStrategy but never knows which concrete implementation it has. Each payment method encapsulates its own dependencies (card validator, PayPal client, blockchain service) behind the shared interface. The context stays untouched when you add Apple Pay next quarter.
Implementation
// The strategy interface defines the contract all payment methods share.
// Using a record for the result gives us immutability for free.
public interface PaymentStrategy {
// Every payment method must implement this single method.
// PaymentContext carries metadata like currency, customer ID, idempotency key.
PaymentResult processPayment(double amount, PaymentContext context);
}
// Immutable result object. Success/failure + transaction reference.
public record PaymentResult(boolean success, String transactionId, String message) {}
// Carries request-scoped data that all strategies need
public record PaymentContext(String customerId, String currency, String idempotencyKey) {}Lambda shortcut for simple strategies
In Java 8+, if your strategy interface has a single method (a functional interface), simple cases collapse into a lambda. I reach for this when the strategy has no state and no dependencies.
// No need for a dedicated class when the logic is a one-liner
CheckoutService flashSale = new CheckoutService(
(amount, ctx) -> new PaymentResult(true, "FLASH-" + ctx.idempotencyKey(),
"Flash sale: free checkout for $" + amount)
);
For strategies with their own configuration or injected services (like CreditCardPayment with its CardValidator), a dedicated class is cleaner. Don't force complex logic into a lambda just to save a file.
How It Works
- Client calls
checkout(cart)on the context. The client chose the strategy earlier (at construction or viasetStrategy). - Context builds a
PaymentContextwith request-scoped data (customer ID, currency, idempotency key). - Context delegates to the strategy by calling
processPayment. It has no idea which concrete strategy is running. - The concrete strategy executes its algorithm, calling whatever external service it needs (Stripe, PayPal, blockchain).
- Result flows back through the strategy to the context to the client. The context never inspects or branches on the result type.
In an interview, you would say: "The context delegates to the strategy through a common interface. The client picks the strategy, the context executes it. Adding a new payment method means adding a new class, not modifying CheckoutService."
Real-World Examples
java.util.Comparatoris the textbook strategy.Collections.sort(list, comparator)accepts any sorting strategy. You never modify the sort algorithm itself, you swap the comparison logic.javax.servlet.Filterchains in Spring Boot use strategy for request processing. Each filter implementsdoFilterindependently. The servlet container composes them without knowing what each one does.java.util.concurrent.RejectedExecutionHandlerinThreadPoolExecutoris a strategy for what happens when the pool is full. The four built-in policies (AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy) are four concrete strategies.
When to Use / When NOT to Use
Use when:
- A class has conditional logic (
if/else,switch) selecting between algorithms at runtime - You have 3+ behavioral variants and expect more in the future
- Each variant has its own dependencies or complex logic
- You want to test each algorithm in isolation without loading the context
Skip when:
- There is only one algorithm and no realistic future variation
- The "strategies" are trivial one-liners that don't justify separate classes (use a lambda or just inline it)
- The algorithm never changes at runtime and compile-time polymorphism (generics or subclassing) would be simpler
If you see a switch on algorithm type, you need Strategy. If you see a single algorithm that has never changed, you don't.
Common Mistakes in Interviews
| # | What candidates say | Why it's wrong | What to say instead |
|---|---|---|---|
| 1 | "Strategy is just polymorphism" | It is polymorphism, but the pattern is specifically about runtime-swappable algorithms in a context. Just saying "polymorphism" misses the delegation structure. | "Strategy uses polymorphism to let a context delegate to interchangeable algorithms at runtime." |
| 2 | "The context creates the strategy" | If the context picks the strategy, you've just moved the if/else inside the context. The whole point is that the client or a factory provides the strategy. | "The client or a factory injects the strategy. The context never chooses." |
| 3 | "I'd use an enum for the strategies" | An enum with a method works for trivial cases but can't hold injected dependencies or complex state. It becomes a God enum. | "Enum works for stateless one-liners. For strategies with dependencies, use classes behind an interface." |
| 4 | "Strategy and State are the same pattern" | Same structure, different intent. Strategy swaps algorithms chosen externally. State manages lifecycle transitions internally. Confusing them in an interview signals shallow understanding. | "Same UML skeleton, different intent. Strategy: client picks algorithm. State: object transitions between behaviors." |
| 5 | "Every if/else needs Strategy" | Two branches with no expected growth don't justify a pattern. Over-engineering is as bad as under-engineering. | "I apply Strategy when there are 3+ variants or when the interviewer signals extensibility is a requirement." |
Strategy vs State vs Template Method
Use this decision flowchart when you're unsure which behavioral pattern fits.
| Dimension | Strategy | State | Template Method |
|---|---|---|---|
| Intent | Choose algorithm | Manage lifecycle | Fix skeleton, vary steps |
| Who decides | Client picks externally | State decides next state | Subclass fills in steps |
| Awareness | Strategies don't know about each other | States know valid transitions | Steps don't know the skeleton |
| Runtime changes | Swapped by client at any time | Transitions triggered internally | Fixed at compile time (inheritance) |
| Typical use | Payment methods, sorting, compression | Order status, connection lifecycle | Report generation, build pipelines |
Test Your Understanding
Quick Recap
- Strategy replaces
if/elseorswitchon algorithm type with a pluggable interface. The context delegates, never branches. - The client (or a factory) picks the strategy. If the context picks it, you've just relocated the conditional.
- Simple stateless strategies collapse into lambdas in Java 8+. Strategies with dependencies or configuration deserve their own class.
- Strategy is Open/Closed compliant: adding a new algorithm means adding a new class. The context and all existing strategies remain untouched.
- Strategy vs State: same UML skeleton, different intent. Strategy swaps externally chosen algorithms. State manages internally driven lifecycle transitions.
- Don't apply Strategy to a single algorithm that has never varied. The pattern earns its keep at 3+ variants with expected growth.
- In an interview, name the pattern, draw the interface with one context and multiple concrete strategies, and explain that the client injects the strategy so the context never knows the concrete type.