# Strategy

> Put each interchangeable algorithm behind one interface so the caller picks a behavior at runtime without branching on it.

- kind: pattern
- category: behavioral
- aliases: Policy
- canonical: https://aipatterns.dev/design-patterns/strategy
- inspired-by: https://refactoring.guru/design-patterns/strategy

## Intent

Define a family of algorithms that do the same job in different ways, give them one shared interface, and let the calling code hold a reference to whichever one it needs. The caller stops knowing *how* the work is done and only knows *that* it can be done.

## Forces

- **One stable caller** vs **many ways to do its core step**: the surrounding workflow rarely changes, but the step in the middle does.
- **Choosing at runtime** vs **readable code**: a growing `switch` over modes works until every new mode touches the same function.
- **Testing each variant in isolation** vs **variants tangled in one class**: you can't unit test the third branch of a 200-line method on its own.
- **Adding a variant** vs **editing code that already works**: each new case should be new code, not a change to shipped code.

## Therefore

**Move each variant into its own object behind a common interface, and have the caller delegate to that object instead of branching.**

The caller (the *context*) receives a strategy through its constructor or a setter. To add a behavior, you write a new strategy; the context does not change. In languages with first-class functions, a strategy is often just a function value. Reach for an interface only when a strategy needs state, configuration, or several related methods.

## Structure

```mermaid
classDiagram
  class Checkout {
    -ShippingStrategy shipping
    +setShipping(s) void
    +total(order) Money
  }
  class ShippingStrategy {
    <<interface>>
    +quote(order) Money
  }
  class FlatRate {
    +quote(order) Money
  }
  class ByWeight {
    +quote(order) Money
  }
  class FreeOverThreshold {
    +quote(order) Money
  }
  Checkout o-- ShippingStrategy
  ShippingStrategy <|.. FlatRate
  ShippingStrategy <|.. ByWeight
  ShippingStrategy <|.. FreeOverThreshold
```

### Participants

- **Strategy** (`ShippingStrategy`): the interface every variant implements. Keep it to what the context actually calls.
- **Concrete strategies** (`FlatRate`, `ByWeight`, …): one algorithm each, with no knowledge of each other.
- **Context** (`Checkout`): holds a strategy, calls it at the right moment, and never inspects its concrete type.
- **Client**: picks a concrete strategy and hands it to the context, usually from configuration or user input.

## Use it when

- A function has a `switch` or `if/else` chain that selects between *algorithms* (not data), and new branches keep arriving.
- Several classes differ only in one method's behavior.
- You need to swap behavior at runtime, such as by tenant, feature flag, A/B bucket, or user setting.
- You want to test each algorithm on its own with no setup for the others.
- An algorithm uses data or dependencies the rest of the class shouldn't see.

## Do not use it when

- There are only two variants and no realistic third. A boolean parameter or a plain `if` is clearer.
- The variant is a single stateless operation. Pass a function (`(order) => Money`); in a language with first-class functions, a one-method interface and three classes is ceremony. Keep the interface when variants carry configuration, mutable state, or a family of related methods.
- The variants change *what* happens across many methods together, depending on the object's current situation. That is [State](https://aipatterns.dev/design-patterns/state.md).
- The variation is a few steps inside a fixed skeleton. [Template Method](https://aipatterns.dev/design-patterns/template-method.md) or plain helper functions fit better.
- The caller would have to know every concrete strategy anyway to choose one, and the choice never changes after construction. A factory function returning the right implementation, or a lookup table, may be all you need.

## Application grill

**Inspect first:** the branching on algorithm choice and every site that switches on it; whether the codebase already passes functions (idiomatic in TypeScript, Python, Go); whether the choice comes from code, configuration, or per-tenant data.

**Missing pieces:** none beyond the language: pass a function. A strategy registry keyed by name earns its place only when the choice arrives from configuration or user input.

**Ask before you build:**

- **Scale:** how many real variants exist today - fewer than three with no third planned is a conditional?
- **Performance:** is dispatch on a hot path where a registry lookup shows (a direct call is cheapest)?
- **Reliability:** when configuration names an unknown strategy, does it fail at startup or at first use?
- **Budget:** N files for N strategies - is the set actually changing, or frozen?
- **Implementation:** what context does each strategy need - one shared interface, or per-strategy parameters?

## Implementation steps

1. Find the branching code and confirm each branch computes the same kind of result from the same inputs.
2. Write the strategy interface from what the context passes in and needs back. Pass data in as arguments; avoid handing strategies the whole context.
3. Move each branch body into its own strategy class or function. Keep behavior identical and run the tests after each move.
4. Give the context a field for the strategy, set through its constructor (or a setter if runtime swapping is required).
5. Replace the branching with one call to the strategy.
6. Move the *selection* logic (which strategy to use) to the edge: configuration, a factory, or a map from key to strategy.

## Example

An online store quotes shipping in three ways, and the marketing team adds a new rule every quarter.

```ts title="shipping.ts"
type Money = number; // cents

interface Order {
  subtotal: Money;
  weightKg: number;
}

interface ShippingStrategy {
  quote(order: Order): Money;
}

class FlatRate implements ShippingStrategy {
  constructor(private readonly fee: Money) {}
  quote(): Money {
    return this.fee;
  }
}

class ByWeight implements ShippingStrategy {
  constructor(private readonly perKg: Money) {}
  quote(order: Order): Money {
    return Math.ceil(order.weightKg) * this.perKg;
  }
}

class FreeOverThreshold implements ShippingStrategy {
  constructor(
    private readonly threshold: Money,
    private readonly fallback: ShippingStrategy,
  ) {}
  quote(order: Order): Money {
    return order.subtotal >= this.threshold ? 0 : this.fallback.quote(order);
  }
}

class Checkout {
  constructor(private shipping: ShippingStrategy) {}

  setShipping(shipping: ShippingStrategy): void {
    this.shipping = shipping;
  }

  total(order: Order): Money {
    return order.subtotal + this.shipping.quote(order);
  }
}

// Selection lives at the edge, not inside Checkout.
const strategies: Record<string, ShippingStrategy> = {
  flat: new FlatRate(499),
  weight: new ByWeight(150),
  promo: new FreeOverThreshold(5000, new FlatRate(499)),
};

const checkout = new Checkout(strategies["promo"]);
console.log(checkout.total({ subtotal: 6200, weightKg: 3.2 })); // 6200
console.log(checkout.total({ subtotal: 1800, weightKg: 3.2 })); // 2299
```

```python title="shipping.py"
from dataclasses import dataclass
from typing import Protocol

@dataclass(frozen=True)
class Order:
    subtotal: int  # cents
    weight_kg: float

class ShippingStrategy(Protocol):
    def quote(self, order: Order) -> int: ...

@dataclass(frozen=True)
class FlatRate:
    fee: int

    def quote(self, order: Order) -> int:
        return self.fee

@dataclass(frozen=True)
class ByWeight:
    per_kg: int

    def quote(self, order: Order) -> int:
        return -(-order.weight_kg // 1) * self.per_kg  # ceil without math import

@dataclass(frozen=True)
class FreeOverThreshold:
    threshold: int
    fallback: ShippingStrategy

    def quote(self, order: Order) -> int:
        return 0 if order.subtotal >= self.threshold else self.fallback.quote(order)

class Checkout:
    def __init__(self, shipping: ShippingStrategy) -> None:
        self.shipping = shipping

    def total(self, order: Order) -> int:
        return order.subtotal + int(self.shipping.quote(order))

STRATEGIES: dict[str, ShippingStrategy] = {
    "flat": FlatRate(499),
    "weight": ByWeight(150),
    "promo": FreeOverThreshold(5000, FlatRate(499)),
}

if __name__ == "__main__":
    checkout = Checkout(STRATEGIES["promo"])
    print(checkout.total(Order(6200, 3.2)))  # 6200
    print(checkout.total(Order(1800, 3.2)))  # 2299
```

```go title="shipping.go"
package main

import (
	"fmt"
	"math"
)

type Order struct {
	Subtotal int // cents
	WeightKg float64
}

// ShippingStrategy is satisfied implicitly by any type with Quote.
type ShippingStrategy interface {
	Quote(o Order) int
}

type FlatRate struct{ Fee int }

func (f FlatRate) Quote(Order) int { return f.Fee }

type ByWeight struct{ PerKg int }

func (b ByWeight) Quote(o Order) int {
	return int(math.Ceil(o.WeightKg)) * b.PerKg
}

type FreeOverThreshold struct {
	Threshold int
	Fallback  ShippingStrategy
}

func (f FreeOverThreshold) Quote(o Order) int {
	if o.Subtotal >= f.Threshold {
		return 0
	}
	return f.Fallback.Quote(o)
}

// QuoteFunc lets a plain function act as a strategy.
type QuoteFunc func(Order) int

func (q QuoteFunc) Quote(o Order) int { return q(o) }

type Checkout struct{ Shipping ShippingStrategy }

func (c Checkout) Total(o Order) int { return o.Subtotal + c.Shipping.Quote(o) }

func main() {
	strategies := map[string]ShippingStrategy{
		"flat":   FlatRate{Fee: 499},
		"weight": ByWeight{PerKg: 150},
		"promo":  FreeOverThreshold{Threshold: 5000, Fallback: FlatRate{Fee: 499}},
		"pickup": QuoteFunc(func(Order) int { return 0 }),
	}
	c := Checkout{Shipping: strategies["promo"]}
	fmt.Println(c.Total(Order{Subtotal: 6200, WeightKg: 3.2})) // 6200
	fmt.Println(c.Total(Order{Subtotal: 1800, WeightKg: 3.2})) // 2299
}
```

```java title="Shipping.java"
import java.util.Map;

public class Shipping {
    record Order(long subtotal, double weightKg) {}

    @FunctionalInterface
    interface ShippingStrategy {
        long quote(Order order);
    }

    record FlatRate(long fee) implements ShippingStrategy {
        public long quote(Order order) { return fee; }
    }

    record ByWeight(long perKg) implements ShippingStrategy {
        public long quote(Order order) { return (long) Math.ceil(order.weightKg()) * perKg; }
    }

    record FreeOverThreshold(long threshold, ShippingStrategy fallback) implements ShippingStrategy {
        public long quote(Order order) {
            return order.subtotal() >= threshold ? 0 : fallback.quote(order);
        }
    }

    static final class Checkout {
        private ShippingStrategy shipping;

        Checkout(ShippingStrategy shipping) { this.shipping = shipping; }

        void setShipping(ShippingStrategy shipping) { this.shipping = shipping; }

        long total(Order order) { return order.subtotal() + shipping.quote(order); }
    }

    public static void main(String[] args) {
        Map<String, ShippingStrategy> strategies = Map.of(
            "flat", new FlatRate(499),
            "weight", new ByWeight(150),
            "promo", new FreeOverThreshold(5000, new FlatRate(499)),
            "pickup", order -> 0 // a lambda is a strategy too
        );
        Checkout checkout = new Checkout(strategies.get("promo"));
        System.out.println(checkout.total(new Order(6200, 3.2))); // 6200
        System.out.println(checkout.total(new Order(1800, 3.2))); // 2299
    }
}
```

## Trade-offs

**Gains**
- New behaviors are new code, and the context stays closed to modification.
- Each algorithm is unit-testable alone.
- Runtime swapping without conditionals.
- Strategies compose: `FreeOverThreshold` wraps another strategy.

**Costs**
- More types, and the reader has to jump to find the active behavior.
- Clients must know the strategies exist in order to pick one; the selection logic has to live somewhere.
- A wide strategy interface forces every variant to implement methods it doesn't need.
- Overkill when a function parameter would do.

## Relations

- [State](https://aipatterns.dev/design-patterns/state.md) has the same shape, but states know about each other and trigger transitions. Strategies are independent and chosen from outside.
- [Template Method](https://aipatterns.dev/design-patterns/template-method.md) varies steps through inheritance at compile time. Strategy varies the whole algorithm through composition at runtime.
- [Command](https://aipatterns.dev/design-patterns/command.md) also wraps behavior in an object, but to queue, log or undo an action, not to pick between ways of computing something.
- [Decorator](https://aipatterns.dev/design-patterns/decorator.md) changes an object's skin by wrapping it. Strategy changes its guts by replacing an internal part.
- [Bridge](https://aipatterns.dev/design-patterns/bridge.md) uses the same delegation structurally, to keep two whole hierarchies independent.

## Agent checklist

- [ ] There are at least three real variants, or a third is concretely planned. Otherwise keep the conditional.
- [ ] If the language has first-class functions, you considered a function type before writing an interface.
- [ ] The strategy interface takes inputs as arguments rather than a reference to the whole context.
- [ ] Selection (which strategy) lives at the composition root, config or a lookup map, not inside the context.
- [ ] Each concrete strategy has its own test, and the original behavior is preserved, verified by running the existing tests.
