CMD Guide
HomeOO & Low-Level DesignBehavioral

Strategy Pattern

Strategy works by pulling one varying piece of behaviour out of a class and behind an interface, then storing a reference to a concrete implementation of that interface as a field — so the owning object calls field.execute(...) and the actual algorithm is decided by which object you assigned to the field, swappable at runtime without touching the caller.

A logistics firm must quote a shipping cost. The cost depends on the carrier mode — standard road, express air, international sea — and each mode prices differently: road is a flat base plus per-kg, air adds a fuel surcharge, sea is cheap per-kg but has a high fixed customs/handling floor. Cramming all three into one calculateCost() with a growing if (mode == ...) chain means every new carrier edits the same method, every pricing tweak risks the others, and the branch list becomes the bottleneck. Strategy makes each pricing rule a separate object implementing one ShippingStrategy interface; the ShippingService holds whichever one the order needs and just calls it.

Worked example — one order, three strategies, real numbers

Order: weight 20 kg, distance 1500 km, declared value $800. Each strategy implements cost(weightKg, distanceKm, declaredValue) with these concrete rules:

StrategyFormulaComputed for 20 kg / 1500 km / $800Result
StandardRoad$5 base + $0.50/kg + $0.01/km5 + (0.50 × 20) + (0.01 × 1500) = 5 + 10 + 15$30.00
ExpressAir$15 base + $1.20/kg + 8% fuel surcharge on subtotal(15 + 1.20 × 20) = 39; 39 × 1.08$42.12
InternationalSea$0.20/kg + 2% of declared value, floored at $25 customs minimum(0.20 × 20) + (0.02 × 800) = 4 + 16 = 20 → below $25 floor$25.00

The trace through the runtime call, after the order picks ExpressAir:

  1. service.setStrategy(new ExpressAir()) — the field now points at the air-pricing object. The service code did not change.
  2. service.quote(20, 1500, 800) runs; internally it does return strategy.cost(20, 1500, 800) — it has no idea which formula it just invoked.
  3. ExpressAir.cost computes 15 + 1.20×20 = 39, applies ×1.08, returns 42.12.
  4. Customer upgrades to international. Caller does service.setStrategy(new InternationalSea()) and re-quotes — same line of service code, different object, returns 25.00 (the $20 raw cost was lifted to the $25 floor inside the strategy, where that rule belongs).
diagram
diagram

Implementation

Java — note the floor rule lives inside InternationalSea, not in the service:

interface ShippingStrategy {
    double cost(double weightKg, double distanceKm, double declaredValue);
}

class StandardRoad implements ShippingStrategy {
    public double cost(double w, double d, double v) {
        return 5.0 + 0.50 * w + 0.01 * d;
    }
}

class ExpressAir implements ShippingStrategy {
    public double cost(double w, double d, double v) {
        double subtotal = 15.0 + 1.20 * w;
        return subtotal * 1.08;            // 8% fuel surcharge
    }
}

class InternationalSea implements ShippingStrategy {
    public double cost(double w, double d, double v) {
        double raw = 0.20 * w + 0.02 * v;
        return Math.max(raw, 25.0);        // $25 customs floor
    }
}

class ShippingService {
    private ShippingStrategy strategy;
    void setStrategy(ShippingStrategy s) { this.strategy = s; }
    double quote(double w, double d, double v) {
        return strategy.cost(w, d, v);     // no idea which formula this is
    }
}

// usage
ShippingService svc = new ShippingService();
svc.setStrategy(new ExpressAir());
svc.quote(20, 1500, 800);   // 42.12
svc.setStrategy(new InternationalSea());
svc.quote(20, 1500, 800);   // 25.00

Go — strategies are just function values, the lightest form of the pattern:

package shipping

import "math"

type Strategy func(weightKg, distanceKm, declaredValue float64) float64

func StandardRoad(w, d, v float64) float64 { return 5 + 0.50*w + 0.01*d }
func ExpressAir(w, d, v float64) float64   { return (15 + 1.20*w) * 1.08 }
func InternationalSea(w, d, v float64) float64 {
    return math.Max(0.20*w+0.02*v, 25)
}

type Service struct{ strategy Strategy }

func (s *Service) SetStrategy(st Strategy) { s.strategy = st }
func (s *Service) Quote(w, d, v float64) float64 { return s.strategy(w, d, v) }

// usage
// svc := &Service{}
// svc.SetStrategy(ExpressAir);       svc.Quote(20, 1500, 800) // 42.12
// svc.SetStrategy(InternationalSea); svc.Quote(20, 1500, 800) // 25.00

Why the naive version is wrong

The tempting first cut puts the branching inside the service:

double quote(String mode, double w, double d, double v) {
    if (mode.equals("road"))      return 5 + 0.50*w + 0.01*d;
    else if (mode.equals("air"))  return (15 + 1.20*w) * 1.08;
    else if (mode.equals("sea"))  return Math.max(0.20*w + 0.02*v, 25);
    throw new IllegalArgumentException(mode);   // forgot a case? runtime blow-up
}

Every new carrier edits this method (violates Open/Closed), the customs-floor rule and the fuel-surcharge rule sit tangled in one place, the rules can't be unit-tested in isolation, and an unknown mode string only fails at runtime instead of being a compile-time type. Strategy turns each branch into a separately testable, separately deployable object and lets the compiler enforce that a strategy was supplied.

Pitfalls

When to use it — and when NOT to

Reach for Strategy when you have a family of interchangeable algorithms for one job (pricing, compression, routing, retry policy), the choice is made at runtime or per-request, and you expect the set to grow. The signal is a growing switch/if on a "kind" that selects behaviour.

vs. if/else (or switch)

The branch chain is fewer lines and zero indirection — perfect when there are 2–3 options that will never grow and never need isolated testing. Cost of moving to Strategy: N extra types/files plus a registry, and one pointer-chase of indirection per call. Choose Strategy when the set is open-ended or each rule deserves its own tests; prefer if/else when the cases are few, stable, and trivial.

vs. Template Method

Template Method also varies behaviour via subclasses, but it fixes the overall skeleton in a base class and lets subclasses fill in steps (compile-time, inheritance). Strategy varies the whole algorithm via composition and can be swapped at runtime. Choose Strategy when you want to change the algorithm on a live object or inject it; prefer Template Method when the high-level flow is invariant and only a few hook steps differ — no runtime swap needed.

vs. State

Structurally identical (an object delegates to a swappable interface). The difference is intent: State objects decide the next state themselves and the client is unaware of transitions; Strategy objects are independent and the client picks one. Choose Strategy when the alternatives don't know about each other; prefer State when behaviour transitions are driven by the object's own lifecycle.

Concrete decision for the shipping case: three pricing rules today, more carriers coming, each rule has its own surcharge/floor logic worth testing alone, and the mode is chosen per order at runtime → Strategy (function-values in Go, classes in Java) wins over the if-chain.

Takeaways


Sources: Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software (the Strategy chapter); Refactoring.Guru, “Strategy”; Freeman & Robson, Head First Design Patterns (2nd ed.), Ch. 1. Re-authored and deepened for this guide — replaced the mismatched travel-planning walkthrough with the logistics shipping-cost example the intro actually poses, added a numeric trace, a naive-version contrast, and Strategy-vs-(if/else, Template Method, State) selection guidance; fixed the mangled “#1## Implementation” heading.

Interview drills & follow-ups

🤖 Don't fully get this? Learn it with Claude

Stuck on Strategy Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Strategy Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Strategy Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Strategy Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Strategy Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Strategy Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes