Design Patterns: Behavioral · Behavioral Patterns

Strategy Pattern

Making an algorithm swappable at runtime by delegating to an interchangeable interface.

  • 5 min read
  • 11 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. How it works, step by step
  4. Where you'll see it
  5. Choosing among the behavioral patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Strategy is the behavioral pattern for interchangeable ways of doing one job: each variant of an algorithm - a pricing rule, a shipping calculation, a sort order - lives behind a shared interface, and the code that needs the result just calls whichever one it was given. You already use it every time you pass a Comparator or a key= function. This skill covers the pattern, how it replaces if chains, and where the "which strategy?" decision should live.

Shipping cost depends on the carrier the customer picks:

Java

double shippingCost(Order order, String carrier) {
    if (carrier.equals("STANDARD")) return 5 + 1.5 * order.weightKg();
    else if (carrier.equals("EXPRESS")) return 15 + 3 * order.weightKg();
    else if (carrier.equals("PICKUP")) return 0;
    // a new carrier means editing this method... again
    throw new IllegalArgumentException(carrier);
}

Python

def shipping_cost(order: Order, carrier: str) -> float:
    if carrier == "STANDARD":
        return 5 + 1.5 * order.weight_kg
    elif carrier == "EXPRESS":
        return 15 + 3 * order.weight_kg
    elif carrier == "PICKUP":
        return 0
    # a new carrier means editing this function... again
    raise ValueError(carrier)

Every new carrier edits the same method, the branches can't be tested in isolation, and the same if chain tends to get copied wherever carriers matter.

We'll go from simple to more technical:

  1. The pattern: a strategy interface, its implementations, and the context that uses one.
  2. Applying it, step by step - and strategies as lambdas and functions.
  3. Where you'll see it: comparators, pricing rules, password encoders.
  4. Choosing the strategy, sharing strategies safely, and Strategy versus State.

The pattern

Strategy puts each variant of an algorithm in its own class behind a shared interface. The code that needs the result (the context) holds a strategy and calls it, without knowing which one it has.

Java

interface ShippingStrategy {
    double cost(Order order);
}

class StandardShipping implements ShippingStrategy {
    public double cost(Order o) { return 5 + 1.5 * o.weightKg(); }
}

class ExpressShipping implements ShippingStrategy {
    public double cost(Order o) { return 15 + 3 * o.weightKg(); }
}

class Checkout {
    private ShippingStrategy shipping;
    void setShipping(ShippingStrategy s) { this.shipping = s; }   // swap at runtime
    double total(Order o) { return o.itemsTotal() + shipping.cost(o); }
}

Python

from abc import ABC, abstractmethod


class ShippingStrategy(ABC):
    @abstractmethod
    def cost(self, order: Order) -> float: ...


class StandardShipping(ShippingStrategy):
    def cost(self, o: Order) -> float:
        return 5 + 1.5 * o.weight_kg


class ExpressShipping(ShippingStrategy):
    def cost(self, o: Order) -> float:
        return 15 + 3 * o.weight_kg


class Checkout:
    def __init__(self, shipping: ShippingStrategy):
        self.shipping = shipping          # swap at runtime by assigning another

    def total(self, o: Order) -> float:
        return o.items_total + self.shipping.cost(o)

In modern Java a strategy interface with one method can also be a lambda: checkout.setShipping(o -> 0.0); for store pickup. In Python that's the usual form: a one-method strategy is just a function, Callable[[Order], float] - checkout.shipping = lambda o: 0.0 - and a registry is a dict from carrier name to function.

How it works, step by step

  1. Identify the part that varies (how shipping is priced).
  2. Put it behind an interface with one clear method.
  3. Write one small class (or lambda) per variant.
  4. The context receives a strategy - through its constructor, a setter, or a lookup map - and just calls it.
  5. A new variant is a new class; nothing else changes.

Where you'll see it

  • Comparator is the everyday example: list.sort(Comparator.comparing(Employee::salary)) passes a sorting strategy. In Python it's the key function: employees.sort(key=attrgetter("salary")).
  • Discount and pricing rules, tax calculations per country, retry/backoff policies, compression or encryption algorithms.
  • Spring picks between strategies constantly - for example different PasswordEncoder implementations behind one interface.

Choosing among the behavioral patterns

If you need to...Reach forIt's about
Let many objects react when something happens, without the source knowing themObserverWho gets told about a change.
Swap one algorithm for another at runtimeStrategy (this lesson)How a task is done - chosen from outside.
Turn a request into an object you can queue, log, undo or redoCommandWhat to do, packaged for later.
Make an object behave differently depending on its current stateStateBehavior that changes itself as the object moves between states.
Fix the steps of an algorithm but let subclasses fill in some of themTemplate MethodThe skeleton stays, the details vary.

Common mistakes

MistakeWhat happensDo this instead
The if chain just moves to every place that picks a strategythe same edit in many places for each new variantchoose in one place - a registry map
A strategy interface for two variants that never changeindirection for nothinga plain if
Strategies needing very different inputs through one interfaceawkward, growing parameter listspass a context or parameter object
Mutable state inside a shared strategyraces between requestskeep shared strategies stateless
Strategies that replace themselves as events arrivea hidden state machineuse the State pattern

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. After refactoring to Strategy, where should the "which strategy?" decision live?

Show answer

In one place - typically a map from key (carrier name, campaign type) to strategy, built once at startup - not in if chains copied to every caller.

2. Your "strategies" start replacing themselves as events arrive. What are you really building?

Show answer

A state machine. With Strategy the client picks the algorithm; when the object switches its own behavior in response to events, model it with the State pattern.

3. You have exactly two variants that will never change. Do they need a strategy interface?

Show answer

No. A simple if is clearer; Strategy pays off when variants keep being added or must be swapped and tested independently.

Key takeaways

  • Each variant of an algorithm lives in its own class (or lambda) behind one interface.
  • The context calls the strategy without knowing which one it has, and it can be swapped at runtime.
  • New variants are new classes - existing code stays closed for modification.
  • Keep the "which strategy?" decision in one place, such as a registry map.
  • Comparator is Strategy you already use every day (in Python, the key= function you pass to sorted).

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

If you need to...Reach for
Swap how one job is doneStrategy
A one-method, stateless varianta lambda / function
A variant with config, several operations or a namea class
Pick by keya registry map built at startup
Variants that differ only in numbersconfiguration for one strategy
Behavior that changes itself with eventsState

Snippets worth knowing by heart

Java

Map<String, ShippingStrategy> SHIPPING = Map.of(
    "STANDARD", o -> 5 + 1.5 * o.weightKg(),
    "EXPRESS",  o -> 15 + 3 * o.weightKg(),
    "PICKUP",   o -> 0.0);

players.sort(Comparator.comparingInt(Player::score).reversed().thenComparing(Player::name));

Python

SHIPPING: dict[str, Callable[[Order], float]] = {
    "STANDARD": lambda o: 5 + 1.5 * o.weight_kg,
    "EXPRESS": lambda o: 15 + 3 * o.weight_kg,
    "PICKUP": lambda o: 0.0,
}

players.sort(key=lambda p: (-p.score, p.name))

The traps

  • The if chain moved to every caller - one registry.
  • Strategies for two fixed variants - just if.
  • Very different inputs per strategy - a context object.
  • Mutable shared strategies - keep them stateless.

Staff-level points, one line each

  • Keep a class when there's configuration, several operations or a needed identity; otherwise a lambda.
  • An inconsistent comparator: Java threw "Comparison method violates its general contract!"; Python silently mis-sorted.
  • Registries need a defined answer for unknown keys (an error with valid keys, or an explicit default).
  • A/B strategies: assign by stable hash of user id + experiment name - random per request flipped users between variants.
  • Different inputs: a context object, or configuration at construction.
  • Test the family: table-driven per strategy, shared properties across all, and the registry itself.
  • Strategy = composition at run time; Template Method = subclassing; State = self-switching.

Say it in an interview

  • "Each carrier is a strategy; the checkout just calls the one it was given."
  • "The registry is the only place that knows every carrier."
  • "Experiment variants are assigned by hashing the user id, so nobody flips."
  • "Every shipping strategy is tested for non-negative, weight-monotonic costs."

Practice

This skill comes with 11 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Strategy Pattern free

Build Sorting Strategies From a Spec

MediumProCoding problemJava · Python

A Comparator is a sorting strategy: the sort algorithm never changes, only the comparison you plug in. Implement ComparatorFactory.fromSpec(spec), which turns a text spec into a comparator.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Strategy Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime