Skip to content
FollowtheDamnCode

Strategy

Put each interchangeable algorithm behind one interface, and let the caller pick which one to plug in.

Big idea

Strategy pulls a family of interchangeable algorithms out of a class and puts each behind the same interface. The class (the context) holds one strategy and delegates to it; callers choose — or swap — the strategy at runtime. New algorithms are new classes, not new branches.

The problem it solves

A ride-hailing app prices trips. Standard fare, surge pricing during peaks, shared rides split between passengers, a flat airport fare. In one price() method that's a growing if mode == … ladder, each branch with its own maths, all in the same class, all re-tested every time a promotion team adds a new mode. The maths for surge can't be reused in the delivery app without dragging in the rest.

Think of it like

Getting to the airport. Taxi, train, or cycling — same goal, different trade-offs in cost and time. You pick based on today's situation (luggage, weather, budget), and the trip's purpose doesn't change. The method of travel is a plug-in to "get to the airport".

Structure

The context delegates to whichever strategy it holds

In code

Four pricing rules, one Trip class
class Standard:
    def price(self, trip):
        return 2.50 + 1.20 * trip.km + 0.25 * trip.minutes

class Surge:
    def __init__(self, multiplier):
        self.multiplier = multiplier
        self.base = Standard()                  # strategies can reuse each other
    def price(self, trip):
        return self.base.price(trip) * self.multiplier

class Shared:
    def __init__(self, riders):
        self.riders = riders
    def price(self, trip):
        return Standard().price(trip) * 1.3 / self.riders

class AirportFlat:
    def price(self, trip):
        return 35.0


class Trip:                                     # the context
    def __init__(self, km, minutes, pricing):
        self.km, self.minutes, self.pricing = km, minutes, pricing
    def fare(self):
        return round(self.pricing.price(self), 2)


for label, pricing in [("standard", Standard()), ("surge x1.8", Surge(1.8)),
                       ("shared x3", Shared(3)), ("airport", AirportFlat())]:
    print(f"{label:11} {Trip(18, 30, pricing).fare():6.2f}")

trip = Trip(18, 30, Standard())
trip.pricing = Surge(1.4)                       # swapped at runtime when demand spikes
print(f"{'re-priced':11} {trip.fare():6.2f}")
output
standard     31.60
surge x1.8   56.88
shared x3    13.69
airport      35.00
re-priced    44.24

Run and edit it in the app.

In Python, when a strategy is a single method with no state, you can skip the classes and pass a function — Trip(18, 30, price=lambda t: 10 + t.km) — with the context calling self.price(self).

In the Strategy pattern, who usually chooses the strategy?

Watch it

Hands on

The shipping calculator below picks its rule with a type flag, and a new "express" option was bolted onto one branch incorrectly — express orders over 50 kg are charged as standard. Restructure it into strategies so each rule is self-contained and the output matches.

Fix it: a pricing ladder that grew a bug
def shipping_cost(kind, kg):
    if kind == "standard" or kg > 50:
        return 5 + 0.5 * kg
    elif kind == "express":
        return 12 + 1.1 * kg
    elif kind == "pickup":
        return 0.0

for kind, kg in [("standard", 10), ("express", 10), ("express", 60), ("pickup", 5)]:
    print(f"{kind:8} {kg:3} kg -> {shipping_cost(kind, kg):6.2f}")
output
standard  10 kg ->  10.00
express   10 kg ->  23.00
express   60 kg ->  78.00
pickup     5 kg ->   0.00

Run and edit it in the app.

When to use it — and when not to

Use it when several variants of an algorithm exist and the choice depends on runtime conditions or configuration: pricing, sorting and ranking, compression, validation rules, retry/back-off policies, route planning. Skip it for two stable variants that never grow — an if is honest — and in Python don't build classes where a plain function argument does the job.

Pros and cons

ProsCons
Swap algorithms at runtimeClients must know the strategies to choose one
Each algorithm isolated and testableMore classes for simple variations
New variants without touching the context (Open/Closed)Strategies may need data the context must expose

How it relates

  • State has the same structure; states switch themselves, strategies are chosen from outside.
  • Template Method varies steps through inheritance; Strategy varies the whole algorithm through composition.
  • Decorator changes an object's outside; Strategy changes its insides.
  • Bridge is the structural cousin: it splits a whole hierarchy, not just one algorithm.
  • Replace Conditional with Polymorphism (Simplifying Conditionals) is how you get here from a ladder.

How does Strategy differ from Template Method?

Key terms

  • Strategy — An interchangeable algorithm behind a shared interface.
  • Context — The object that holds a strategy and delegates the varying work to it.
  • Higher-order function — A function that takes another function as an argument — Python's lightweight strategy.