BehavioralGoF classic

Strategy Pattern

Define a family of algorithms, put each in its own class, and make them interchangeable at runtime without changing the code that uses them.

Practise this pattern

1. Problem & the anti-pattern

Violation: Open/closed principle

A checkout computes discounts. Every new promotion adds another branch to one growing conditional, so each change risks breaking the others and nothing can be tested alone.

Anti-pattern: Every rule edits one conditional

Adding a promotion means editing the same function, so every change can break every other rule.

checkout.ts (anti-pattern)Procedural branching
function total(cart: Cart, promo: string) {
  if (promo === 'PERCENT10') return cart.sum * 0.9;
  else if (promo === 'FLAT50') return Math.max(0, cart.sum - 50);
  else if (promo === 'BOGO') { /* … */ }
  return cart.sum;
}

2. Structure & UML

Interactive schematic

Class composition and runtime delegation.

Strategy pattern class diagram. Classes: Checkout, DiscountStrategy (interface), PercentOff, FlatOff. Relationships: Checkout aggregates DiscountStrategy; PercentOff implements DiscountStrategy; FlatOff implements DiscountStrategy.
100%

3. Structural participants

Participants
Architectural roleResponsibility
StrategyThe common interface every algorithm implements (DiscountStrategy).
Concrete strategiesOne class per algorithm (PercentOff, FlatOff).
ContextHolds a strategy and delegates to it (Checkout). It never knows which concrete class it has.

4. Code implementation

3 languages
strategy.ts
interface DiscountStrategy {
  apply(total: number): number;
}


class PercentOff implements DiscountStrategy {
  constructor(private percent: number) {}
  apply(total: number) {
    return Math.floor((total * (100 - this.percent)) / 100);
  }
}


class Checkout {
  constructor(private strategy: DiscountStrategy) {}
  setStrategy(s: DiscountStrategy) {
    this.strategy = s;
  }
  total(prices: number[]) {
    return this.strategy.apply(prices.reduce((a, b) => a + b, 0));
  }
}

5. Trade-offs

Key advantages

  • Open/closed: new discounts are new classes; Checkout never changes.
  • Testable in isolation: each rule is a tiny class with one method to test.
  • Swappable at runtime: pick the rule per customer, campaign or A/B bucket.

Liabilities & overhead

  • More classes: a two-branch if is simpler than two strategy classes.
  • Callers must choose: someone still decides which strategy to pass in.
  • Premature for one rule: an interface with a single implementation is noise.

6. When not to use

Premature abstraction

If the rule has one or two stable variants and no one asks to add more, keep the conditional. Extract a strategy the moment a third variant or a runtime switch appears.

Rule of thumb: three distinct algorithms, or one that changes at runtime.

8. Practical blueprint challenges

View all problems

No problems use this pattern yet.