Stackify is now BMC. Read theBlog

Strategy Pattern: Definition, Examples, and Best Practices

By: Stackify Team
  |  February 26, 2025
Strategy Pattern: Definition, Examples, and Best Practices

Strategy is one of the most well-known design patterns, and luckily, it’s also one of the easiest to understand and use. That doesn’t mean the strategy pattern isn’t valuable. Quite the contrary: this pattern is incredibly powerful in enabling you to write code that is low coupled, easy to read and maintain, adheres to the SOLID principles and the dependency injection pattern.

To help you understand the strategy pattern, this post covers the following:

  • A basic overview of what a design pattern is
  • An introduction to the strategy pattern
  • A few use case examples
  • Advantages and limitations of the pattern
  • Best practices you must be aware of

Prerequisites

To understand the examples, you’ll need some programming experience. Some level of familiarity with C# helps, but the examples will be simple enough that even readers without C# experience will be able to follow along.

If you want to run the examples as we go along, then you’ll need to have .NET installed on your machine and a code editor/IDE, such as Visual Studio Code.

Introduction to the Strategy Pattern

Without further ado, let’s dig into the strategy design pattern.

Understanding Design Patterns

This is how Wikipedia defines design patterns at the time of this writing:

“In software engineering, a software design pattern or design pattern is a general, reusable solution to a commonly occurring problem in many contexts in software design. A design pattern is not a rigid structure that can be transplanted directly into source code. Rather, it is a description or a template for solving a particular type of problem that can be deployed in many different situations.”

As a software engineer, it’s extremely beneficial for you to learn about design patterns since they allow you to solve problems in tried-and-tested ways. Also, design patterns serve as a form of communication. Solutions provided by design patterns are commonly known and understood by a sizeable portion of programmers, and adopting design patterns reveals the intent of code in a way that improves readability and maintainability.

Overview of the Strategy Pattern

The famous book, Design Patterns: Elements of Reusable Object-Oriented Software, defines the motivation for the strategy pattern as follows:

“Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.”

In plain terms, you have a task that needs to be done in your program and several ways to do it. Therefore, you would be wise to create some isolation between the code that needs to perform this task and the ways in which this task can be performed. Creating a degree of isolation increases maintainability and helps you avoid code duplication and high coupling.

The strategy pattern avoid these pitfalls by enabling you to do the following:

  • Express the desired behavior in a high-level way
  • Create as many implementations of the behavior as you need in such a way that they’re interchangeable
  • Inform the client code to depend on only the high-level behavior, not the concrete implementations

How does that work in practice?

How the Strategy Pattern Works

Learning how the strategy pattern works starts by understanding its components.

Definition and Responsibilities of Each Component

The strategy pattern relies on three components: strategy, concrete strategy, and context.

  • Strategy defines the high-level behavior
  • One or more concrete strategies implement the behavior, or algorithm, in specific ways
  • Context is the client which uses the strategy

In C#, a common way of implementing the three components would be the following:

  • Create an interface for the strategy
  • Have the context depend on that interface, usually via constructor dependency injection
  • Creating as many concrete classes as necessary to implement the strategy interface, each performing the task in its unique ways

Interaction Between Components

Who decides which one of the concrete strategies should be used? Normally, this is the job of the context’s client. In other words, the code that uses the context is usually responsible for deciding, which one of the available strategies to provide to the context, based on your criteria.

In short, the interaction goes like this:

  • Some portion of the code instantiates a new instance of the context class
  • The code chooses one of the available strategies and provides that to the context via a method, property, constructor injection or using a provider pattern
  • The code calls some method on the context class
  • The context delegates the call to its instance of the strategy, unaware of the concrete strategy that’s being used

Things will become clearer after you see an example.

Example Scenario: Implementing a Simple Strategy Pattern

Let’s say you have a payroll application and would like to calculate employees’ end-of-year bonus according to their performances. After the annual review, employees receive one of the three possible classifications: meet expectations, exceed expectations, and below expectations, and receive bonuses as follows:

  • Meet expectations: 10 percent of their base salary
  • Exceed expectations: 20 percent of their base salary plus a fixed amount of $1,000
  • Below expectations: 5 percent of their base salary

To implement this, first we should create the strategy. Let’s use an interface for that:

public interface IBonusCalculationStrategy
{
    decimal CalculateBonus(decimal baseSalary);
}

Now we’ll create three classes, one for each of the performance classifications:

public class MeetExpectationsStrategy : IBonusCalculationStrategy
{
    public decimal CalculateBonus(decimal baseSalary)
    {
        return baseSalary * 0.10m;
    }
}

public class ExceedExpectationsStrategy : IBonusCalculationStrategy
{
    public decimal CalculateBonus(decimal baseSalary)
    {
        return (baseSalary * 0.20m) + 1000m;
    }
}

public class BelowExpectationsStrategy : IBonusCalculationStrategy
{
    public decimal CalculateBonus(decimal baseSalary)
    {
        return baseSalary * 0.05m;
    }
}

It’s now time for the context. In our code, this will be the Employee class itself, which will store the actual state needed for the calculation:

public class Employee
{
    public string Name { get; set; } = "";
    public decimal BaseSalary { get; set; }
    private IBonusCalculationStrategy _bonusStrategy;

    public Employee(string name, decimal baseSalary, IBonusCalculationStrategy strategy)
    {
        Name = name;
        BaseSalary = baseSalary;
        _bonusStrategy = strategy;
    }

    public decimal CalculateYearEndBonus()
    {
        return _bonusStrategy.CalculateBonus(BaseSalary);
    }
}

Finally, we’ll use the code by instantiating the context (that is, the employee), passing it a concrete implementation and then calculating the bonus:

class Program
{
    static void Main()
    {
        // Create an employee with initial "Meet Expectations" rating
        var employee = new Employee(
            "John Doe", 
            50000m, 
            new MeetExpectationsStrategy()
        );

        // Calculate bonus
        Console.WriteLine($"Employee: {employee.Name}");
        Console.WriteLine($"Base Salary: ${employee.BaseSalary}");
        Console.WriteLine($"Bonus: ${employee.CalculateYearEndBonus()}");
    }
}

Advantages of Using the Strategy Design Pattern

Using the strategy design pattern brings you many benefits:

  1. Reduces code duplication
  2. Reduces code coupling because the context doesn’t need to be aware of the many possible different implementations for the strategy
  3. Simplifies adding a new implementation: just create a new strategy class
  4. Reduces the number of switch statements across the code and can also be used as an alternative for inheritance (for instance, having a PoorPerformantEmployee, ExcelentPerformantEmployee class, and so on)

The third item from the list above shows that the strategy design pattern is a great way to make your code adhere to the OCP (open-closed principle). The OCP is one of the SOLID principles, and it states that a given module or class should be open to expansion but close to modification. In other words, you should write your application in such a way that, in order to change or add new behavior, you create new code instead of editing the existing code. The strategy pattern is one way to accomplish that goal.

Finally, even though we’ve been talking about different behaviors, strategy pattern can be used to execute multiple implementations of the same behavior. Think, for instance, of sorting algorithms. A bubble sort is implemented – and performs – very differently than a quick sort, but both have the same externally observable behavior. Regardless, the input gets sorted correctly.

Limitations and Considerations of the Strategy Pattern

Like any other design pattern, the strategy pattern isn’t a silver bullet. Strategy patterns have potential limitations and pitfalls you should be aware of before adopting.

First of all, using a higher number of classes increased complexity of your code. Most of the time, you won’t be using a gigantic number of strategies, but if that’s the case, you might want to reconsider adopting this pattern.

The code that creates the context must choose which strategy to use. This creates coupling between the client and strategy classes, as the client needs to know about all available strategies. Using the Abstract Factory pattern can help reduce this coupling.

Finally, there’s potential performance overhead due to the increased number of allocations (in a scenario in which the client creates many strategies and switches between them).

Strategy Pattern: Best Practices

Before wrapping up, here are some best practices related to strategy. Some of those are C# specific, but most can be applied to any object-oriented language:

  • Prefer lambdas if feasible. For strategies that are simple, consider using .NET’s delegate types, such as Func or Action
  • Keep strategies interfaces small and focused, which makes implementation and usage simpler
  • Maintain strategy implementations stateless. Put all state in the context class to make the strategies purely about behavior, so they’re easier to understand, use, and share
  • Consider implementing a default strategy. That makes usage of the context class simpler and prevents null reference exceptions

Strategically Design Your Way into Good, Performant Code

The strategy design pattern is quite simple when compared to other patterns, but don’t underestimate the value the pattern provides. You can really improve your code and overall application performance by using the pattern wisely.

Indiscriminate usage of strategies can significantly impact performance. That’s why using an application monitoring tool such as Stackify is key. With Stackify, you can monitor your application in real time, identify concerning trends and proactively resolve issues before they turn into a real issue. To see how Stackify can help you use strategy design pattern and optimize overall application performance, start your free Stackify trial today.

Improve Your Code with Retrace APM

Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.

Learn More