The Strategy Design Pattern is a behavioral design pattern that lets you define a family of algorithms, put each one into a separate class, and makes their objects interchangeable — allowing the algorithm to vary independently from the clients that use it.
It’s particularly useful in situations where:
if-else
or switch
statements) for every variation.When you have multiple ways to achieve the same goal, you might use branching logic inside a class to handle different cases. For example, a PaymentService
might use if-else
to choose between credit card, cash, or UPI.
But as more payment types are added, this approach becomes hard to scale, violates the Open/Closed Principle, and makes your code harder to test and maintain.
The Strategy Pattern solves this by encapsulating each behavior in its own class and delegating the responsibility to the right strategy at runtime — keeping your core logic clean, extensible, and testable.
Let’s walk through a real-world example to see how we can apply the Strategy Pattern to build a more flexible and maintainable workflow for handling varying behaviors.
Imagine you're building a shipping cost calculator for an e-commerce platform.
As with most real-world applications, shipping charges can vary based on different business rules or external providers.
Here are some common strategies you may need to support:
A quick and naive solution might be to implement all of this logic inside a single class, using a long chain of conditionals:
While it may seem fine initially, this design quickly becomes brittle and problematic as your system evolves:
Every time a new shipping strategy is added—say “Free Shipping for Prime Members” or “Eco Delivery with Carbon Offset”—you must modify the ShippingCostCalculatorNaive
class. This makes the class open to modification rather than being closed for changes and open to extension.
The if-else
chain becomes increasingly large and unreadable as more strategies are introduced. It clutters your code and makes debugging harder.
Each strategy is tangled inside one method, making it harder to test individual behaviors independently. You must set up entire Order
objects and manually select the strategy type just to test one case.
If different services (e.g., checkout, returns, logistics) use similar logic, you may end up duplicating these conditional blocks in multiple places.
The calculator class is doing too much. It knows how to handle every possible algorithm for shipping cost, rather than focusing on orchestrating the calculation.
We need a cleaner approach, something that allows:
This is where the Strategy Design Pattern comes into play.
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.
In simpler terms: rather than hardcoding logic with if-else
or switch
statements, you delegate the behavior to strategy objects.
The pattern allows a client (like a shipping calculator or payment processor) to plug in a specific behavior at runtime without changing the underlying logic of the system.
ShippingStrategy
)A common interface that declares the algorithm's method. All concrete strategies implement this interface.
These are individual implementations of the Strategy
interface . Each class encapsulates a different algorithm.
ShippingCostService
)This is the main class that uses a strategy to perform a task. It holds a reference to a Strategy
object and delegates the calculation to it. The context doesn’t know or care which specific strategy is being used—it just knows that it has a strategy that can calculate a shipping cost.
The client is responsible for:
Context
Context
at runtime if neededLet's refactor our shipping cost calculator.
ShippingStrategy
)We begin by defining a common interface for all shipping strategies. This allows the context class (ShippingCostService
) to interact with any strategy interchangeably.
Each concrete strategy will implement this interface and provide its own algorithm for calculating shipping cost.
Each shipping strategy encapsulates its logic in a dedicated class. This makes the behavior modular and easy to extend or test.
ShippingCostService
)The context class maintains a reference to a ShippingStrategy
and delegates the calculation to it.
This class is completely agnostic to which algorithm it’s using , it simply delegates to whatever strategy is currently set.
Let’s see how the client dynamically switches between different strategies at runtime:
Notice how clean this is!
ShippingCostService
.This is the power of the Strategy Pattern: it separates what you do from how you do it, leading to clean, extensible, and maintainable code.