Skip to main content

Strategy Design Pattern

1. What this document is about

This document is about using the Strategy Pattern to deal with behavioral variation in real systems — especially when the flow is stable, but the rules are not. It fits enterprise systems where behavior changes based on context: customer type, tenant, regulation, contract, or policy.

It is not a good fit for simple flows, stable rules, or situations where a conditional is clearly enough and is unlikely to change over time.


2. Why this matters in real systems

In enterprise systems, variation is rarely accidental. It usually comes from:

  • business rules per customer or plan
  • policies that evolve over time
  • regulatory exceptions
  • logic that starts simple and grows fast

When Strategy is not used, the outcome is predictable:

  • services full of if/else
  • large switch blocks with “temporary” cases
  • business logic spread across layers
  • fear of change because no one understands the impact

This is not just about ugly code. It leads to fragile systems that are hard to evolve and expensive to maintain. Strategy matters because it contains change, and change is the default state of any long-lived system.


3. Core concept (mental model)

Think of Strategy like:

You have one execution flow that stays the same. What changes is how a decision is made inside that flow.

The common mistake is thinking:

"Which branch do I execute?"

The right question is:

"Which behavior applies here?"

The Strategy Pattern separates:

  • the when (orchestration, flow)
  • from the how (algorihm, rule, policy)

The flow stays clean. The variation is isolated.


4. How it works (step-by-step)

  1. Identify the point of variation Something that:

    • changes often
    • grows with the business
    • generates repeated conditionals
  2. Define a stable contract

    • Simple interface
    • No infrastructure concerns
    • Describes what is done, no how
  3. Create isolated implementations

    • One behavior per strategy
    • No knowledge of other strategies
    • No coupling to the flow
  4. Centralize the selection

    • The decision of which strategy to use lives in one place
    • Based on context, not scattered rules
  5. Execute through the abstraction

    • The flow calls the strategy
    • It does not know — and does not care — which one it is

Important invariants

  • The flow never knows business rules
  • Strategies do not select themselves
  • Adding a new strategy does not break existing ones

5. Minimal concrete example (.NET)

Scenario

Discount calculation varies by customer type

Contract

IDiscountStrategy.cs

public interface IDiscountStrategy
{
decimal Calculate(decimal orderTotal);
}

Strategies

RegularCustomerDiscount.cs

public sealed class RegularCustomerDiscount : IDiscountStrategy
{
public decimal Calculate(decimal orderTotal)
=> orderTotal * 0.05m;
}

PremiumCustomerDiscount.cs

public sealed class PremiumCustomerDiscount : IDiscountStrategy
{
public decimal Calculate(decimal orderTotal)
=> orderTotal * 0.15m;
}

Selection and execution

DiscountService.cs

public sealed class DiscountService
{
private readonly IReadOnlyDictionary<CustomerType, IDiscountStrategy> _strategies;

public DiscountService(IEnumerable<IDiscountStrategy> strategies)
{
_strategies = strategies.ToDictionary(
s => s switch
{
PremiumCustomerDiscount => CustomerType.Premium,
RegularCustomDiscount => CustomerType.Regular,
_ => throw new InvalidOperationException();
}
)
}

public decimal Apply(CustomerType customerType, decimal orderTotal)
{
var strategy = _strategies[customerType];
return strategy.Calculate(orderTotal);
}
}

The important part here is not the syntax, but the effect:

  • no business rules in the flow
  • no scattered conditionals
  • each variation is isolated and testable

6. Design trade-offs

AspectStrategyIf / Switch
Initial complexityHigherLower
EvolutionControlledChaotic
TestabilityExcellentPoor
Long-term readabilityStableDegrades quickly
Runtime costNegligibleNegligible

What you gain

  • change isolation
  • clear intent
  • predictable evolution

What you pay

  • more types
  • more abstraction
  • requires discipline

7. Common mistakes and misconceptions

  1. Creating a strategy for every tiny variation This leads to overengineering fast.

  2. Letting the strategy decide if it applies This breaks the model and introduces hidden coupling.

  3. Stateful strategies This creates concurrency issues and unpredictable behavior.

  4. Using Strategy as a universal solution Not every variation justifies this cost.


8. When NOT to use this

Do not use Strategy when:

  • the rule is stable and simple
  • variation is rare
  • the domain does not justify abstraction
  • a conditional is clearer and will remain so

Using Strategy too early creates accidental complexity.


9. Key takeaways

  • Strategy separates flow from variation
  • It exists to handle change, not elegance
  • Strategy selection is a first-class design concern
  • Strategies should be simple and stateless
  • Overuse fragments systems
  • Underuse creates rigidity
  • The pattern only pays off in evolving systems

10. High-Level Overview

Visual representation of the Strategy Pattern, highlighting centralized behavior selection, isolated strategies, and a stable execution flow.

Strategy Pattern — Structural View (Enterprise Mental Model)Strategy Pattern — Structural View (Enterprise Mental Model)Application Layer (Flow / Orchestration)Strategy Selection (Centralized)Domain Behavior (Variation Encapsulated)DiscountService(StableFlow)Apply(customerType,orderTotal):decimalTheflowisstable.ItdoesNOTcontainbusiness-rulebranching.Invariant:-Noif/switchonbusinessrulesinsidetheflow-Behaviorisselected,notbranchedStrategySelector(DecisionPoint)Select(context):IDiscountStrategyCentralizes:"Whichbehaviorapplieshere?"Typicalinputs:-customerType-tenantId-region-contract/policy-regulatorycontextFailuremode:-missingmapping/invalidcontextIDiscountStrategy(StableContract)Calculate(orderTotal):decimalRegularCustomerDiscount(ConcreteStrategy)Calculate(orderTotal):decimalPremiumCustomerDiscount(ConcreteStrategy)Calculate(orderTotal):decimalContractmustremainstableovertime.Keepstrategies:-Stateless-Thread-safe-TestableinisolationWhythisstructuresurviveschange:-Addnewstrategieswithoutrewritingtheflow-Localizechangetothevariantbehavior-Testeachstrategyindependentlyasksforstrategy(basedoncontext)returnsselectedbehaviorexecutesCalculate()plantuml-src dLLDRnit4BtlhnZe92EHxVWe52rSsm6Cs8aG2V6b5ueSQGdlaWkIAtdR1kYFw2_CByaZbxihf4s1z69hoVbuy-RDCEXGCQs34u7t7Qr42EmCVVdhxtZOoj0wKT4dpKUQtndSDKvxfaSs0UUFLd5rLXJ-MPj6E56JBuMoHstsj1ELvvERHiXdiUTrw93GiGp2xAjJ0sdhnXe4_hx9qOdcIQjGqjNbvSbvBLuUsViONl_EzERgiYXoFffTDqsbfGZQ6deG7JkQlwtiaIxeWvCb-rWxxivc93nThrRqHq4aA-4zpMwrbxO597O7BVatCry7iGMw6A7tIB6Y2z6hcAgRozO7MxFRT0s_9kiKVbhGTKPBKYnrBIgOVoxmnzY0IlM-36HtALGAj2cPTZ6kzkHJmlDqVXz8MVRq_iC6TA45sj2szTgW-8LhWMlhX94b-3ylUWzp44uB4vRfSq7lBUdTXJ_g84koapj5Tq_QUAsOGWQGdNxbKXoqTGaFL-WCgzSHVCx72ePiLAgd-7n2_gYiTNABFPZVeCTG5JgbUWhNTm-df0y-lOjrOFqMjFdelBBQXEoLhmVo--zvv8LVGYJxVcnVZlZlj0-1KfW9d5_IxAdKe6axb2-YbC1IoOv_cVKCeyjGLmNccZRuWUPJ3UIZm0Pzk5Vvq_4U1GtcDcQLuQAnq6etcRIL0BAESbLzohT2LwrZgZ5-Gy9QUn_7hWR6-Fy2W0weGuskt-dGhQsZZCOMppz5mQHEtHaf6XyXZ9tQV4nqwxWATaB2_nyaJqDoawlgVU-JmT2j6r7957d-dmEI9V4nCU5kCgj3IgH23kauJ3ii0ttp_nAj7DUwhNyWqTKF9ieq_FJd-JbGVdjmDSfpQmCWH7ts1hqwCwDK0o3qaLhNRmYo1wose6lEe_-EkI7VWuLe1w6WDm62zpv_Rqh7GYsys0r2tN2EYDZQsog9uHlvfAstMFmyJU4IR3nxsk6ddvIm5PvLt3F3M1QZG_JEjIzH2buQCssNSUQcJACjlx1i0yPmOXnlq0bfmFfKTbXYQNtshnYJRx733t2J9TwLD3WBkbQe2O_Rn1DXDPQsZUmUdGvnbawsuO8Uh4ph8OUXODDzNhCJxe59Oe4LCb02JXKtO19hfkgAWTAl?>Strategy Pattern — Structural View (Enterprise Mental Model)Strategy Pattern — Structural View (Enterprise Mental Model)Application Layer (Flow / Orchestration)Strategy Selection (Centralized)Domain Behavior (Variation Encapsulated)DiscountService(StableFlow)Apply(customerType,orderTotal):decimalTheflowisstable.ItdoesNOTcontainbusiness-rulebranching.Invariant:-Noif/switchonbusinessrulesinsidetheflow-Behaviorisselected,notbranchedStrategySelector(DecisionPoint)Select(context):IDiscountStrategyCentralizes:"Whichbehaviorapplieshere?"Typicalinputs:-customerType-tenantId-region-contract/policy-regulatorycontextFailuremode:-missingmapping/invalidcontextIDiscountStrategy(StableContract)Calculate(orderTotal):decimalRegularCustomerDiscount(ConcreteStrategy)Calculate(orderTotal):decimalPremiumCustomerDiscount(ConcreteStrategy)Calculate(orderTotal):decimalContractmustremainstableovertime.Keepstrategies:-Stateless-Thread-safe-TestableinisolationWhythisstructuresurviveschange:-Addnewstrategieswithoutrewritingtheflow-Localizechangetothevariantbehavior-Testeachstrategyindependentlyasksforstrategy(basedoncontext)returnsselectedbehaviorexecutesCalculate()plantuml-src dLLDRnit4BtlhnZe92EHxVWe52rSsm6Cs8aG2V6b5ueSQGdlaWkIAtdR1kYFw2_CByaZbxihf4s1z69hoVbuy-RDCEXGCQs34u7t7Qr42EmCVVdhxtZOoj0wKT4dpKUQtndSDKvxfaSs0UUFLd5rLXJ-MPj6E56JBuMoHstsj1ELvvERHiXdiUTrw93GiGp2xAjJ0sdhnXe4_hx9qOdcIQjGqjNbvSbvBLuUsViONl_EzERgiYXoFffTDqsbfGZQ6deG7JkQlwtiaIxeWvCb-rWxxivc93nThrRqHq4aA-4zpMwrbxO597O7BVatCry7iGMw6A7tIB6Y2z6hcAgRozO7MxFRT0s_9kiKVbhGTKPBKYnrBIgOVoxmnzY0IlM-36HtALGAj2cPTZ6kzkHJmlDqVXz8MVRq_iC6TA45sj2szTgW-8LhWMlhX94b-3ylUWzp44uB4vRfSq7lBUdTXJ_g84koapj5Tq_QUAsOGWQGdNxbKXoqTGaFL-WCgzSHVCx72ePiLAgd-7n2_gYiTNABFPZVeCTG5JgbUWhNTm-df0y-lOjrOFqMjFdelBBQXEoLhmVo--zvv8LVGYJxVcnVZlZlj0-1KfW9d5_IxAdKe6axb2-YbC1IoOv_cVKCeyjGLmNccZRuWUPJ3UIZm0Pzk5Vvq_4U1GtcDcQLuQAnq6etcRIL0BAESbLzohT2LwrZgZ5-Gy9QUn_7hWR6-Fy2W0weGuskt-dGhQsZZCOMppz5mQHEtHaf6XyXZ9tQV4nqwxWATaB2_nyaJqDoawlgVU-JmT2j6r7957d-dmEI9V4nCU5kCgj3IgH23kauJ3ii0ttp_nAj7DUwhNyWqTKF9ieq_FJd-JbGVdjmDSfpQmCWH7ts1hqwCwDK0o3qaLhNRmYo1wose6lEe_-EkI7VWuLe1w6WDm62zpv_Rqh7GYsys0r2tN2EYDZQsog9uHlvfAstMFmyJU4IR3nxsk6ddvIm5PvLt3F3M1QZG_JEjIzH2buQCssNSUQcJACjlx1i0yPmOXnlq0bfmFfKTbXYQNtshnYJRx733t2J9TwLD3WBkbQe2O_Rn1DXDPQsZUmUdGvnbawsuO8Uh4ph8OUXODDzNhCJxe59Oe4LCb02JXKtO19hfkgAWTAl?>