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.
Introduction
The Strategy pattern extracts a family of interchangeable algorithms behind a common interface. A context delegates work to the selected strategy, so the algorithm can vary without embedding every variant in the context's control flow.
TL;DR / mental model
Treat each algorithm as a plug-in. The client or a factory selects the strategy, the context supplies common request data, and the strategy performs the variable work. Use this when the changing concern is the algorithm itself, not the lifecycle of the context.
Problem and Context
You're building a payment system. The checkout class needs to process credit cards, PayPal, and crypto. Without a pattern, you may end up with a growing if/else chain that puts pressure on Open/Closed every time someone adds a new payment method.
Three methods today, seven next quarter. Each branch has completely different logic, different dependencies, and different error handling. Testing one branch means loading the entire class, and adding another method expands the same conditional again.
Here is what changes when you apply the Strategy pattern.
Participants and Structure
CheckoutService is the context: it holds a reference to a PaymentStrategy without depending on a concrete implementation. Each payment method encapsulates its own dependencies (card validator, PayPal client, blockchain service) behind the shared interface. In this design, adding Apple Pay means adding another strategy rather than changing the checkout algorithm.
Idiomatic Example and Implementation Notes
// 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 can collapse into a lambda. This is a good fit 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, Step by Step
- 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 short: the context delegates through a common interface, the client or factory selects the strategy, and a new payment method can be added as another implementation without putting its algorithm into 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.
Trade-offs, Alternatives, and 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
An algorithm-type switch is a signal to consider Strategy, not proof that the pattern is required. If there is one stable algorithm, a direct method call is usually clearer.
Strategy separates algorithms and makes each one independently testable, but it adds an interface, objects, and selection or dependency-injection wiring. A lambda is a lighter alternative for a small stateless rule; an enum can be adequate for a closed set of simple cases; a factory can centralize selection while still returning the strategy interface. Use State when behavior changes because the context moves through an internal lifecycle, and Template Method when a fixed algorithm skeleton varies through inheritance.
30-Second Explanation
Strategy puts interchangeable algorithms behind a common interface. The context delegates to whichever strategy the client or factory provides, so adding or replacing an algorithm does not require a growing conditional inside the context. The cost is extra types and wiring, so a direct call or lambda may be better for a stable, tiny rule.
5-Minute Explanation
Define a PaymentStrategy contract such as processPayment(amount, context). Each concrete strategy owns the dependencies and error handling for one payment flow. CheckoutService is the context: it builds request-scoped data and delegates to the injected strategy without knowing whether it is credit card, PayPal, or crypto. Selection can happen at construction time, at runtime, or in a factory. Contrast this with State, where the object changes behavior as its own lifecycle advances, and Template Method, where inheritance fixes the algorithm skeleton.
Common Mistakes and Misconceptions
| # | Common assumption | Why it is incomplete | Better mental model |
|---|---|---|---|
| 1 | "Strategy is just polymorphism" | It uses polymorphism, but the pattern also defines a context that delegates to interchangeable algorithms. Naming only the language feature misses that structure. | "Strategy uses polymorphism so a context can delegate to interchangeable algorithms." |
| 2 | "The context creates the strategy" | If the context chooses and constructs every concrete strategy, the selection conditional has merely moved into the context. | "The client, composition root, or factory supplies the strategy; the context executes it." |
| 3 | "An enum is never a strategy" | An enum with behavior can work for a small, closed set of stateless cases. It becomes awkward when variants need injected dependencies or complex state. | "Use an enum for simple closed cases; use classes behind an interface when variants need independent dependencies or testing." |
| 4 | "Strategy and State are the same pattern" | They can share the same class shape, but Strategy swaps an externally chosen algorithm while State manages internal lifecycle transitions. | "Strategy: the client chooses the algorithm. State: the object transitions between behaviors." |
| 5 | "Every if/else needs Strategy" | Two stable branches may not justify an interface, classes, and injection wiring. Over-engineering adds its own maintenance cost. | "Use Strategy when variants or runtime selection are a real design concern, not just because a conditional exists." |
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
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 supports Open/Closed at the chosen extension point: adding a new algorithm can mean adding a new class without changing the context or existing strategies.
- 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 stable algorithm. Use it when multiple variants and expected growth justify the extra indirection.
- Explain the pattern through its participants: the client or factory selects, the context delegates, and concrete strategies own the algorithm-specific work.
Related Patterns
- State pattern - Changes behavior through an internal lifecycle rather than an externally selected algorithm.
- Template method pattern - Fixes an algorithm skeleton in a base class while subclasses vary individual steps.
- Factory pattern - Can centralize selection and construction of concrete strategies.
- Dependency Injection pattern - Supplies the strategy to the context without hard-coding its concrete type.
Related Articles
The observer pattern decouples event producers from consumers. A subject notifies all registered observers of state changes without knowing who they are or what they do.
The state pattern encapsulates state-specific behavior into separate objects, eliminating large switch statements and making each state's logic independently testable.
Learn the five SOLID principles by building a real order-processing system in Java, with before and after code for every principle.