Last Updated: February 23, 2026
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:
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.
Two characteristics define the pattern:
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.
FlatRateShipping, WeightBasedShipping)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.
The Strategy workflow is straightforward:
Step 1: The client creates a concrete strategy object (e.g., FlatRateShipping).
Step 2: The client passes the strategy to the context, either through the constructor or a setter.
Step 3: The context stores the strategy reference in a field typed to the Strategy interface.
Step 4: When the context needs to run the algorithm, it calls the strategy's method. The context does not know or care which concrete strategy is behind the interface.
Step 5: To change behavior, the client swaps in a different strategy. The context code does not change at all.
Let us refactor our shipping calculator using the Strategy pattern. Here is the class diagram for the refactored design:
The ShippingStrategy interface defines the contract. Four concrete strategies (orange) each encapsulate a different shipping algorithm. The ShippingCostService context holds a strategy reference and delegates all calculations to it.
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, and that is the whole point.
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 deliberately 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. No conditional logic inside ShippingCostService. Strategies are encapsulated, reusable, and easy to test. Adding a new strategy (say, FreeShippingForPrimeMembers) only requires creating a new class that implements ShippingStrategy. No changes to the service or existing strategies. You can switch strategies at runtime without breaking any existing functionality.
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.
Let us work through a second example to reinforce the pattern. This time, we are building a payment processing system that supports multiple payment methods: credit card, PayPal, and cryptocurrency. Each method has a different processing flow, but the checkout service should not care which one is being used.
The same pattern, different domain. The CheckoutService has no idea whether it is charging a credit card, sending a PayPal request, or initiating a crypto transfer. It just calls pay() on whatever strategy is plugged in. Adding a new payment method (bank transfer, Apple Pay, buy-now-pay-later) means creating one new class. Nothing else changes.