Strategy Pattern
Imagine a food delivery application that can calculate delivery charges in different ways. One customer may choose standard delivery, another may choose express delivery, while a premium customer may receive free delivery. The application needs different algorithms, but it should not become a giant collection of if-else statements.
This is where the Strategy Pattern becomes useful. Instead of embedding every algorithm inside one class, we place each algorithm in its own class and allow the application to select the appropriate strategy at runtime.
What Is the Strategy Pattern?
The Strategy Pattern is a behavioral design pattern that defines a family of interchangeable algorithms, encapsulates each algorithm separately, and allows the algorithm to be selected or changed without modifying the client code.
In simple terms:
Strategy Pattern means: put different ways of performing an operation behind a common interface, then choose the required way at runtime.
The important idea is not simply creating multiple classes. The real goal is to separate what the application wants to do from how it does it.
Why Does the Strategy Pattern Exist?
A common design problem appears when one class supports several variations of the same behavior.
For example, consider a payment system:
- Pay using credit card.
- Pay using UPI.
- Pay using net banking.
- Pay using a digital wallet.
A beginner might write everything inside one method:
void pay(String method, double amount) {
if (method.equals("CARD")) {
// Card payment
} else if (method.equals("UPI")) {
// UPI payment
} else if (method.equals("WALLET")) {
// Wallet payment
} else if (method.equals("NET_BANKING")) {
// Net banking payment
}
}
This may work initially, but every new payment method requires modifying the same class. As the number of algorithms increases, the class becomes harder to understand, test, and maintain.
The Strategy Pattern moves those algorithms into separate strategy classes.
A Simple Real-World Analogy
Think about traveling from one city to another.
Your goal is the same: reach the destination.
But you may choose different strategies:
- Travel by car.
- Travel by train.
- Travel by airplane.
- Travel by bus.
The destination does not change, but the method of reaching it does.
The traveler acts as the context, while each transportation method represents a strategy.
Core Structure of the Strategy Pattern
The Strategy Pattern usually contains three important roles:
| Role | Responsibility |
|---|---|
| Strategy | Defines a common interface for all algorithms. |
| Concrete Strategy | Provides a specific implementation of the algorithm. |
| Context | Uses a strategy without depending on its implementation details. |
Basic Strategy Example
Let us build a simple payment system.
First, define the common strategy interface:
interface PaymentStrategy {
void pay(double amount);
}
Now create concrete strategies.
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");
}
}
class WalletPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid ₹" + amount + " using Wallet");
}
}
Each class knows only how to perform its own payment algorithm.
Now create the context:
class PaymentService {
private PaymentStrategy strategy;
public PaymentService(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void makePayment(double amount) {
strategy.pay(amount);
}
}
The client can now select the required strategy:
public class Main {
public static void main(String[] args) {
PaymentStrategy strategy =
new UpiPayment();
PaymentService service =
new PaymentService(strategy);
service.makePayment(2500);
}
}
The output is:
Paid ₹2500.0 using UPI
Notice something important: PaymentService does not know how UPI payment works. It simply knows that a PaymentStrategy can perform payment.
How the Strategy Pattern Works Internally
The flow can be understood in four steps:
- Step 1: Define a common strategy interface.
- Step 2: Create separate classes for different algorithms.
- Step 3: Give the selected strategy to the context.
- Step 4: The context delegates the operation to the strategy.
The context therefore depends on an abstraction rather than a concrete algorithm.
The Context decides when an operation should happen, while the Strategy decides how it should happen.
Changing a Strategy at Runtime
One of the most useful characteristics of this pattern is that the strategy can be changed.
For example:
class PaymentService {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void makePayment(double amount) {
strategy.pay(amount);
}
}
Now the same context can use different strategies:
PaymentService service = new PaymentService(); service.setStrategy(new CreditCardPayment()); service.makePayment(1000); service.setStrategy(new UpiPayment()); service.makePayment(2000); service.setStrategy(new WalletPayment()); service.makePayment(500);
The PaymentService object remains the same. Only its behavior changes.
Strategy Pattern with Sorting
The Strategy Pattern becomes even clearer when we consider sorting.
Suppose an application needs to sort data using different approaches:
- Ascending order.
- Descending order.
- Sorting by name.
- Sorting by price.
Instead of putting every sorting rule inside one class, define a strategy:
interface SortStrategy {
void sort(int[] numbers);
}
Concrete strategies can implement different sorting algorithms:
class AscendingSort implements SortStrategy {
@Override
public void sort(int[] numbers) {
System.out.println("Sorting in ascending order");
}
}
class DescendingSort implements SortStrategy {
@Override
public void sort(int[] numbers) {
System.out.println("Sorting in descending order");
}
}
The context simply uses whichever sorting strategy it receives.
class SortService {
private final SortStrategy strategy;
public SortService(SortStrategy strategy) {
this.strategy = strategy;
}
public void execute(int[] numbers) {
strategy.sort(numbers);
}
}
Strategy Pattern and Lambda Expressions
Java makes the Strategy Pattern particularly convenient because functional interfaces and lambda expressions can represent small strategies without requiring separate classes.
For example:
@FunctionalInterface
interface DiscountStrategy {
double calculate(double price);
}
Different strategies can be supplied using lambdas:
DiscountStrategy noDiscount =
price -> price;
DiscountStrategy tenPercentDiscount =
price -> price * 0.90;
DiscountStrategy twentyPercentDiscount =
price -> price * 0.80;
A context can then use the selected strategy:
class ShoppingCart {
private final DiscountStrategy strategy;
public ShoppingCart(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double calculateTotal(double price) {
return strategy.calculate(price);
}
}
Usage becomes very concise:
ShoppingCart cart =
new ShoppingCart(tenPercentDiscount);
double total = cart.calculateTotal(5000);
System.out.println(total);
This is still the Strategy Pattern. The implementation is simply expressed using a lambda instead of a named concrete strategy class.
Strategy Pattern vs if-else
Consider an application that calculates shipping charges.
A conditional approach might look like this:
if (type.equals("STANDARD")) {
// Standard shipping
} else if (type.equals("EXPRESS")) {
// Express shipping
} else if (type.equals("SAME_DAY")) {
// Same-day shipping
}
This is not automatically bad. For two or three genuinely simple alternatives, an if-else or switch may be perfectly reasonable.
The problem appears when the algorithms become large, change frequently, require independent testing, or continue growing.
With Strategy Pattern:
interface ShippingStrategy {
double calculate(double weight);
}
class StandardShipping implements ShippingStrategy {
public double calculate(double weight) {
return weight * 10;
}
}
class ExpressShipping implements ShippingStrategy {
public double calculate(double weight) {
return weight * 25;
}
}
Now each algorithm has its own home.
Strategy Pattern and Dependency Inversion
The Strategy Pattern naturally supports the Dependency Inversion Principle.
Instead of this:
class PaymentService {
private UpiPayment payment = new UpiPayment();
}
the context depends on the abstraction:
class PaymentService {
private final PaymentStrategy strategy;
public PaymentService(PaymentStrategy strategy) {
this.strategy = strategy;
}
}
This makes the context easier to extend and test because different strategy implementations can be supplied from outside.
Strategy Pattern and Open/Closed Principle
Suppose a system initially supports three discount algorithms. Later, the business introduces a new festival discount.
With Strategy Pattern, a new strategy can be introduced:
class FestivalDiscount implements DiscountStrategy {
@Override
public double calculate(double price) {
return price * 0.70;
}
}
The existing context does not need to know the internal calculation.
This supports the Open/Closed Principle: behavior can be extended by adding new implementations rather than repeatedly modifying the core context.
Common Mistakes Beginners Make
- Creating strategies for trivial variations: Not every small condition requires a design pattern.
- Keeping all logic inside the context: If the context still contains large conditional algorithms, the separation has not been achieved.
- Making strategies depend heavily on the context: Strategies should generally encapsulate their own algorithm rather than becoming tightly coupled to the context.
- Using inheritance unnecessarily: Strategy is primarily about composition and interchangeable behavior.
- Creating dozens of meaningless strategy classes: Excessive abstraction can make a simple system harder to understand.
Best Practices
- Define a small and meaningful strategy interface.
- Keep each strategy focused on one algorithm or behavior.
- Prefer constructor injection when the strategy is required for the lifetime of the context.
- Use a setter only when changing the strategy at runtime is genuinely required.
- Keep strategies independent from unnecessary context details.
- Use lambdas for small, stateless strategies where they improve readability.
- Use named classes when the algorithm is substantial or deserves independent documentation and testing.
- Do not introduce Strategy Pattern merely to eliminate a small if statement.
When Should You Use the Strategy Pattern?
The Strategy Pattern is particularly useful when:
- Multiple algorithms perform the same general operation.
- The algorithm may change at runtime.
- Conditional logic is becoming large or difficult to maintain.
- Different algorithms need independent testing.
- New variations are expected to be added regularly.
- The client should remain independent of concrete algorithm implementations.
When Should You Avoid It?
The Strategy Pattern is not automatically the correct solution.
Avoid introducing it when:
- There is only one algorithm and no realistic variation is expected.
- The alternatives are extremely small and a simple conditional is clearer.
- The abstraction would create many classes without providing meaningful flexibility.
- The additional indirection makes a simple problem harder to understand.
A design pattern should solve a design problem, not become the problem itself.
Strategy Pattern vs Template Method
Both patterns deal with variations in behavior, but they use different mechanisms.
| Strategy Pattern | Template Method |
|---|---|
| Uses composition. | Uses inheritance. |
| Behavior can usually be changed by supplying another strategy. | Behavior is defined through subclasses. |
| Focuses on interchangeable algorithms. | Defines a fixed overall algorithm structure with customizable steps. |
| Generally favors runtime composition. | Generally establishes behavior through subclass implementation. |
Strategy Pattern vs State Pattern
Strategy and State can look similar because both may use composition and multiple implementation classes.
The difference is primarily in intent.
| Strategy | State |
|---|---|
| Represents alternative ways of performing an operation. | Represents different states of an object. |
| The client or configuration commonly selects the algorithm. | The object's state often determines its behavior. |
| Focuses on algorithm selection. | Focuses on behavior changing according to state. |
Strategy Pattern vs Factory Pattern
These patterns are frequently confused because both may involve selecting an implementation.
Their responsibilities are different.
| Strategy | Factory |
|---|---|
| Encapsulates interchangeable behavior. | Encapsulates object creation. |
| Focuses on how an operation is performed. | Focuses on which object should be created. |
| Usually participates in executing an algorithm. | Usually participates in producing an object. |
A real application can even use both together: a Factory can create the appropriate Strategy implementation, and the Context can execute that Strategy.
Testing Benefits
Strategy Pattern can make testing easier because each algorithm is isolated.
For example, a discount strategy can be tested independently:
DiscountStrategy strategy =
new TenPercentDiscount();
double result = strategy.calculate(1000);
System.out.println(result);
The test does not need to construct the entire shopping application.
This isolation becomes particularly valuable when strategies contain complex business rules.
Real-World Applications
The Strategy Pattern appears naturally in systems that support interchangeable rules or algorithms, such as:
- Payment processing.
- Shipping calculation.
- Discount calculation.
- Tax calculation.
- Sorting and filtering.
- Compression algorithms.
- File processing.
- Authentication mechanisms.
- Pricing rules.
- Routing and navigation algorithms.
A More Practical Example: Discount Engine
Consider an e-commerce application with different customer discounts.
interface DiscountStrategy {
double calculateDiscount(double amount);
}
class RegularCustomerDiscount
implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.05;
}
}
class PremiumCustomerDiscount
implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.15;
}
}
class FestivalDiscount
implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.25;
}
}
The pricing service can remain independent:
class PricingService {
private final DiscountStrategy strategy;
public PricingService(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double finalPrice(double amount) {
double discount =
strategy.calculateDiscount(amount);
return amount - discount;
}
}
Now different business rules can be plugged into the same pricing service.
PricingService service =
new PricingService(
new PremiumCustomerDiscount());
double price = service.finalPrice(10000);
System.out.println(price);
If a new discount policy is introduced later, the existing pricing calculation does not need to become a large conditional structure.
Learning Checkpoint
Ask yourself:
- What is the common operation shared by the strategies?
- Which part represents the changing algorithm?
- Which class acts as the context?
- Can the algorithm be replaced without changing the context?
- Would a simple conditional be clearer for this particular problem?
Interview Insights
Question 1: What is the Strategy Pattern?
It is a behavioral design pattern that encapsulates a family of interchangeable algorithms behind a common interface and allows the appropriate algorithm to be selected independently of the client.
Question 2: What problem does Strategy solve?
It reduces tightly coupled conditional logic when a class needs to support multiple interchangeable algorithms or behaviors.
Question 3: Does Strategy use inheritance?
The concrete strategies commonly implement a common interface, but the Context itself uses composition to receive and work with a strategy.
Question 4: Can a Strategy be changed at runtime?
Yes. If the design allows it, the Context can receive or replace its Strategy during execution.
Question 5: What is the difference between Strategy and State?
Strategy focuses on selecting among alternative algorithms, while State focuses on changing an object's behavior according to its current state.
Question 6: Can lambda expressions implement Strategy?
Yes. When the strategy interface is a functional interface, Java lambda expressions can provide compact strategy implementations.
Quick Revision
| Concept | Key Idea |
|---|---|
| Strategy | Encapsulates interchangeable algorithms. |
| Strategy Interface | Defines the common behavior. |
| Concrete Strategy | Implements one specific algorithm. |
| Context | Uses the selected strategy. |
| Composition | Allows behavior to be supplied or replaced. |
| Lambda Strategy | Provides a concise implementation for functional strategies. |
| Main Benefit | Separates changing algorithms from the core context. |
Final Takeaway
The Strategy Pattern is about giving an object different ways to accomplish the same kind of task without forcing those variations into one complicated class.
Instead of asking a class to understand every possible algorithm, we give each algorithm its own focused implementation and connect them through a common abstraction.
Remember the core idea: encapsulate what varies, program to an abstraction, and make algorithms interchangeable.
