Last Updated: December 31, 2025
The Strategy Design Pattern is a behavioral pattern that lets you define a family of algorithms, encapsulate each one in its own class, and make them interchangeable at runtime.
At its core, the Strategy pattern is about separating "what varies" from "what stays the same."
Instead of embedding multiple algorithms inside a single class with conditional logic, you extract each algorithm into its own strategy class. The main class (context) delegates the work to whichever strategy is currently plugged in.
This pattern becomes valuable when:
A common first approach is to use if-else or switch statements inside a class to handle different behaviors. If you are building a payment system, you might check "is it credit card? PayPal? cryptocurrency?" and handle each case inline.
This works initially, but as the number of cases grows, your class becomes a maintenance nightmare.
The Strategy pattern solves this by turning each branch of that conditional into a separate class. The main class holds a reference to a strategy interface and calls it without knowing which concrete implementation is being used.
You can swap strategies at runtime, add new ones without touching existing code, and test each algorithm in isolation.
Let us walk through a real-world example to see how the Strategy Pattern transforms messy conditional code into a clean, extensible design.
Imagine you are building an e-commerce platform. One of the features you need is a shipping cost calculator. Sounds simple enough, but shipping costs can be calculated in many different ways depending on business rules:
Your first implementation might look like this:
This works. The client passes a method name, and the calculator returns the appropriate cost. But watch what happens as the business evolves.
While it may seem fine initially, this design quickly becomes brittle and problematic as your system evolves:
Every new shipping method requires modifying the ShippingCalculator class. You are constantly opening a class that should be stable. Each modification risks breaking existing functionality.
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.
What if another part of your application needs shipping calculations? You might copy this logic, and now you have two places to maintain.
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 an approach where:
This is exactly what the Strategy Pattern provides.
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
In plain terms: instead of a giant if-else chain deciding which algorithm to run, you create separate classes for each algorithm. These classes all implement the same interface. The client holds a reference to this interface and calls it without knowing which concrete class is behind it.
Think about how you might travel from your home to the airport. You have several options:
Each of these is a "travel strategy." You (the traveler) decide which strategy to use based on factors like cost, time, and convenience. The important point is that you do not change how you "travel" as a concept. You just swap out the method.
The Strategy pattern works the same way.
The Strategy Pattern involves three key components:
ShippingStrategy)Declares the interface common to all supported algorithms. The Context uses this interface to call the algorithm defined by a ConcreteStrategy.
Implements the algorithm using the Strategy interface. Each concrete strategy encapsulates a specific 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.
Let us refactor our shipping calculator using the Strategy Pattern.
ShippingStrategy)First, we define a common interface that all shipping strategies must implement:
This interface is simple and focused. Every strategy takes an order and returns a cost. The interface says nothing about how the cost is calculated.
We use an interface rather than an abstract class because shipping strategies have no shared implementation. If they did (say, logging before calculation), an abstract class with a template method might be appropriate.
Each shipping algorithm becomes its own class:
Notice how each class is focused on a single responsibility. The DistanceBasedShipping class knows about zones. The WeightBasedShipping class knows about weight calculations. Neither knows about the other.
The context class holds a reference to a strategy and delegates calculations to it:
The context is simple. It stores a strategy, provides a way to change it, and delegates calculations. It does not know or care which concrete strategy is being used.
Here is how the client uses the pattern:
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.
Let us evaluate what the Strategy Pattern has given us:
The ShippingCostCalculator is now closed for modification. To add a new shipping method, you create a new strategy class. The existing code remains untouched.
Each strategy class has one job: calculate shipping cost using a specific algorithm. The calculator has one job: orchestrate the calculation by delegating to a strategy.
Each strategy can be unit tested in isolation. You do not need to set up complex scenarios to reach a specific branch. Just create the strategy and call calculateCost().
Strategies can be swapped at any time. A user might start with standard shipping and upgrade to express during checkout. The system handles this seamlessly.
We use type-safe strategy objects instead of fragile string comparisons. The compiler catches mistakes.
The calculator and strategies are separate objects. Changes to one do not ripple through the others.