What Is the Strategy Pattern?

What Is the Strategy Pattern?

A GPS app can route you by car, by bike, or on foot — same destination, completely different method. The Strategy pattern gives your code that exact same freedom to swap how something gets done, without touching what it is trying to achieve.

01

The Big Idea, in One Breath

Package up a family of interchangeable methods for accomplishing the same task, and let calling code pick whichever one fits the moment — without rewriting the surrounding logic each time.

Open a maps app and ask it to get you across town. Before it shows you a single turn, it usually asks one simple question first: are you driving, cycling, or walking? The destination never changes — but the method for getting there changes completely depending on your answer. Driving avoids one-way streets in a certain direction; walking cuts straight through a park; cycling favours dedicated bike lanes. Three entirely different routes, three entirely different sets of rules, all serving the exact same goal of getting you from A to B.

The Strategy pattern captures that exact same idea in software. It is a design pattern that packages up a family of interchangeable methods — or “strategies” — for accomplishing the same task, and lets the calling code pick whichever one fits the moment, without needing to rewrite the surrounding logic every time the method changes. The goal stays fixed; only the approach for reaching it gets swapped in and out.

Everyday Analogy

Think about packing for a trip. Whether you are flying, driving, or taking a train, your underlying goal is identical — get your belongings from home to your destination safely. But the actual packing method changes completely depending on which one you choose: strict weight limits and a compact carry-on for flying, versus loose, flexible bags stuffed into a car boot for driving. You do not redesign the entire concept of “packing” each time — you simply swap in the packing strategy that fits your current mode of travel.

What makes this small shift so valuable is what it frees the surrounding code from needing to know. A checkout screen that calculates shipping costs does not need to understand the inner workings of every possible shipping method available today, and it definitely should not need to be rewritten every time the company partners with a new delivery courier next year. All it needs to know is that whatever shipping method it is handed will know how to calculate its own cost correctly. That single, simple trust — “I do not need to know how you work, only that you will do your job” — is the quiet engine driving the entire Strategy pattern, and it is what allows software built around it to keep growing new capabilities without becoming more fragile as it does.

02

What the Strategy Pattern Really Is

A behavioural design pattern that defines a family of interchangeable algorithms, encapsulates each one separately, and makes them swappable independently of the code that uses them.

Formally, the Strategy pattern is a behavioural design pattern that defines a family of interchangeable algorithms, encapsulates each one separately, and makes them swappable independently of the code that uses them. It belongs to the behavioural family — patterns concerned with how objects communicate and share responsibility — because its real focus is on how a task gets carried out, not on how objects come into existence or how they are structurally connected.

  1. A family of algorithms. Several different ways of accomplishing the same underlying task, each written as its own separate, self-contained piece.
  2. Swappable independently. The code that uses a strategy does not need to change at all when a different strategy is chosen — it simply works with whichever one it is handed.
i
In plain words

“What does the Strategy pattern actually do?” is really asking: it separates “what needs to happen” from “exactly how it happens,” so the “how” can be swapped out freely, without the “what” ever needing to notice or care.

It is worth being precise about what makes this genuinely different from simply writing a function with a few different behaviours built directly into it using an if-statement. Strategy specifically packages each approach as its own separate, independent unit — meaning new approaches can be added later without touching any of the existing ones, and each approach can be tested, understood, and modified entirely on its own.

There is a subtler benefit worth naming explicitly too: because each approach lives in its own separate class or function, it becomes genuinely reusable in situations that have nothing to do with where it was first written. A discount calculation strategy built for an online store’s checkout page could, without any changes at all, be reused inside an internal reporting tool that needs to estimate historical discount totals — something that would have been far messier to extract cleanly from inside one large, tangled conditional function handling a dozen unrelated responsibilities at once.

03

A Little History

One of the eleven behavioural patterns catalogued in 1994 by the Gang of Four. Often the first behavioural pattern engineers reach for, thanks to how immediately intuitive its core idea is.

Strategy is one of the eleven behavioural patterns catalogued in 1994 by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides — the four authors nicknamed the “Gang of Four” — in their book Design Patterns: Elements of Reusable Object-Oriented Software. It is often introduced early in that catalogue’s behavioural section, likely because its core idea is so immediately intuitive compared to some of its more elaborate siblings.

The underlying idea predates the formal name by a fair margin, though. Sorting algorithms, in particular, had already been treated as interchangeable “plug-in” components in various programming libraries for years before 1994 — a sorting routine that could accept different comparison methods was effectively already applying the Strategy pattern’s core idea, well before anyone had written the concept down formally as a named, catalogued pattern.

i
Good to know

Strategy is frequently cited, alongside Observer, as one of the two most immediately practical and widely applied behavioural patterns in everyday professional development — its usefulness shows up constantly, in situations engineers encounter on ordinary projects rather than only in specialised, complex systems.

It is worth appreciating why Strategy earned this reputation for approachability, compared to some of the more elaborate behavioural patterns in the same 1994 catalogue. Patterns like Visitor or Interpreter solve genuinely intricate, somewhat specialised problems that many working engineers might encounter only rarely across an entire career. Strategy, by contrast, solves a problem nearly every engineer runs into within their first few months of professional work: a function that started simple and has slowly grown a long, unwieldy chain of conditional branches as new requirements kept arriving. That universality — a problem virtually everyone recognises immediately from their own experience — is a large part of why Strategy tends to be one of the very first behavioural patterns most engineers learn to reach for confidently.

04

Why Not Just Use If/Else?

A conditional chain works for a while. But every new option means editing the same function, re-testing everything, and taking on more risk with each addition.

To understand why Strategy earns its place, it helps to look closely at what happens when a task with multiple possible approaches is handled with a straightforward chain of conditional checks instead.

Python — a growing conditional chain
def calculate_shipping(order, method):
    if method == "standard":
        return order.weight * 0.5
    elif method == "express":
        return order.weight * 1.2 + 5
    elif method == "overnight":
        return order.weight * 2.0 + 15
    # Every new shipping option means editing this exact
    # function again, and re-testing everything inside it.
    raise ValueError("Unknown shipping method")

This works, at least for a while. But every new shipping option — international, same-day, bulk freight — means reopening this exact function, adding another branch, and re-testing the entire thing to make sure the new addition has not disturbed anything already working. As the number of options grows, this single function grows right alongside them, becoming longer, harder to read, and riskier to touch with every passing month.

Growing complexity

One function, many jobs

A single function ends up responsible for every possible approach at once, rather than each approach being its own clean, focused unit.

Risky changes

Touching one risks all

Adding or fixing one branch means editing code that also contains every other branch, raising the chance of an accidental, unrelated bug.

Hard to test

Testing in isolation is awkward

Testing just the “express” logic still means going through the same shared function that contains all the other options too.

Poor reuse

Locked inside one function

If another part of the program needs just the “overnight” calculation on its own, it cannot easily reuse it without pulling in the whole conditional chain.

The Strategy pattern exists specifically to dissolve this problem, breaking one overloaded, ever-growing function into a family of small, independent, individually testable pieces.

It is worth being honest about why this problem tends to sneak up on teams gradually rather than announcing itself on day one. A shipping calculator with just one method, “standard,” genuinely does not need Strategy — a single plain function is the simplest, most sensible choice at that stage. The trouble builds slowly: a second shipping option gets added, then a third, each addition feeling small and manageable in isolation. Few teams pause at any single addition to ask “should this whole approach be restructured?” — the discomfort tends to arrive only once the function has quietly grown past the point where any one person can read it top to bottom and confidently understand every branch, which is usually well after the ideal moment to have made the switch.

05

The Anatomy of the Strategy Pattern

Three cooperating roles: Strategy (the shared interface), ConcreteStrategy (each specific approach), Context (the code that uses whichever strategy it is given).

A properly structured Strategy implementation is built from three cooperating roles.

Context holds a reference to a strategy Strategy a shared interface ConcreteStrategyA ConcreteStrategyB
The Context holds whichever ConcreteStrategy it is given, without needing to know which one it is.
  • Strategy. A shared interface describing what every interchangeable approach must offer — usually one method, like calculate() or execute().
  • ConcreteStrategy. A specific implementation of that interface — one particular way of performing the task, standing alongside its siblings as an equal alternative.
  • Context. The class that holds a reference to whichever strategy it is currently using, and delegates the actual work to it, without knowing or caring which specific strategy it is.

The relationship between Context and its strategies is deliberately one of composition rather than inheritance — the Context does not extend or inherit from any particular strategy, it simply holds a reference to one, the same way a car holds a set of tyres without being a kind of tyre itself. This distinction matters because composition can be changed freely at any moment, simply by swapping which object is being held, while inheritance is typically locked in once a class is written. That flexibility to change strategies on the fly, even while a program is already running, is one of Strategy’s most practically valuable features, and it is a direct consequence of choosing composition as the underlying mechanism.

06

Building a Strategy, Step by Step

Each shipping method as its own small class. A Checkout Context that just trusts whichever strategy it is holding. Adding a new option later needs zero changes to what already exists.

Watching the shipping calculator evolve from the earlier conditional chain into a proper Strategy implementation makes the benefit immediately clear.

Python — each shipping method as its own strategy
class StandardShipping:
    def calculate(self, order):
        return order.weight * 0.5

class ExpressShipping:
    def calculate(self, order):
        return order.weight * 1.2 + 5

class OvernightShipping:
    def calculate(self, order):
        return order.weight * 2.0 + 15

class Checkout:
    def __init__(self, shipping_strategy):
        self.shipping_strategy = shipping_strategy  # the Context

    def get_shipping_cost(self, order):
        # Checkout has no idea which strategy it is holding —
        # it just trusts it to calculate a cost correctly.
        return self.shipping_strategy.calculate(order)

checkout = Checkout(ExpressShipping())
print(checkout.get_shipping_cost(order))

Adding a new shipping option — say, international shipping — now means writing one small, self-contained InternationalShipping class, with its own calculate() method. Nothing about Checkout needs to change at all, and none of the existing shipping classes are touched, tested, or put at risk in the process.

It is also worth showing how naturally this structure supports swapping strategies after the fact, since that flexibility is one of the pattern’s biggest selling points. If a customer changes their mind partway through checkout, switching from express to overnight shipping is as simple as replacing the object the Checkout instance is holding — checkout.shipping_strategy = OvernightShipping() — and the very next call to get_shipping_cost() automatically uses the new approach, with zero other changes required anywhere in the surrounding code. Achieving that same on-the-fly flexibility with the earlier conditional chain would have meant passing a new method string around and re-running the entire branching function again from scratch.

07

Real-World Use Cases

Payment processing, sorting, compression, discounts, route planning, validation — anywhere the same task genuinely has several interchangeable ways of being carried out.

Strategy shows up constantly in everyday professional software, often in situations engineers reach for it instinctively without necessarily naming the pattern out loud.

Payment processing

Swappable payment methods

A checkout flow can accept credit cards, digital wallets, or bank transfers, each implemented as its own strategy behind one shared “pay” interface.

Sorting

Different orderings, same list

A sorting function can accept different comparison strategies, letting the exact same sorting logic arrange data by price, rating, or date.

Compression

Different formats, same goal

A file archiving tool can support several compression strategies, letting users choose faster compression or smaller file size as needed.

Discount rules

Different promotions, same checkout

An online store’s discount logic can swap between percentage-off, buy-one-get-one, or flat-rate strategies without touching the core checkout flow.

Route planning, mentioned in this guide’s opening analogy, is itself a textbook real-world Strategy use case — a navigation app’s underlying “find a route” logic stays fixed, while the actual pathfinding approach swaps completely depending on whether the traveller is driving, cycling, or walking.

Validation logic is another domain worth calling out specifically, since it is one many engineers encounter early and often. A signup form might need to validate a password using different rules depending on context — a consumer app might require eight characters and one number, while a banking app might require twelve characters, a symbol, and no repeated sequences. Rather than cramming every possible rule set into one sprawling validation function riddled with conditional checks, each rule set becomes its own small validation strategy, and the form simply asks whichever strategy is currently active whether a given password passes. Adding a new, stricter rule set for a future enterprise product tier becomes a matter of writing one new class, entirely separate from the rules already serving existing customers.

08

Strategy and the Open/Closed Principle

Adding a new strategy needs zero changes to existing code. That is the Open/Closed Principle in direct, practical action — and Strategy is one of the clearest ways of achieving it.

Strategy has a close, natural relationship with the Open/Closed Principle — one of the well-known SOLID design principles, stating that software should be open for extension but closed for modification. In plain terms: it should be possible to add new behaviour without editing code that already works and has already been tested.

Look back at the shipping example. Adding InternationalShipping means writing one new class — nothing about Checkout, or any of the existing shipping strategies, needs to be reopened at all. That is the Open/Closed Principle in direct, practical action, and Strategy is one of the clearest, most commonly cited ways of achieving it for situations involving multiple interchangeable approaches to the same task.

i
Rule of thumb

If adding a new “way of doing X” to your system means editing a function that already handles several other ways of doing X, that is a strong signal a Strategy refactor — pulling each approach into its own class — could make future additions considerably safer.

It is worth being clear-eyed about the boundary of this benefit, too. Strategy keeps a system open for extension specifically around which approach gets used for one particular task — it does not automatically make every other part of that system flexible. A checkout flow could apply Strategy beautifully to its shipping calculations while leaving several other rigid, hard-to-extend decisions untouched elsewhere in the same codebase. The Open/Closed Principle describes a broad aspiration for how an entire system should behave as it grows; Strategy is simply one focused, well-proven tool for achieving that aspiration in situations where a task genuinely has multiple interchangeable ways of being carried out.

09

Strategy vs. State Pattern

Almost identical structural skeleton, genuinely different problems. Strategy is about choosing an approach; State is about an object’s behaviour changing over time.

These two patterns share an almost identical structural skeleton — both involve a context holding a reference to an interchangeable object — which is exactly why they are so frequently confused, despite solving genuinely different problems.

Strategy

  • The calling code deliberately chooses which approach to use.
  • Strategies are typically unaware of each other entirely.
  • Answers: “which method should accomplish this task?”

State

  • The object itself often switches its own current state internally.
  • States frequently know about, and transition to, one another.
  • Answers: “how should this object behave right now?”

The clearest way to separate them: Strategy is about choosing an approach, usually decided once, from outside. State is about an object’s behaviour changing over time, often triggered by the object itself, moving through a sequence — a traffic light shifting from red to green to yellow is State; a checkout choosing between three shipping calculators is Strategy.

A useful diagnostic question, when a design genuinely looks like it could be either: “does the object being modelled naturally move through a sequence of distinct phases on its own, or is something outside simply picking one fixed option among several equals?” A media player naturally moves through playing, paused, and stopped phases, often triggering its own transitions internally — that is State. A checkout selecting a shipping calculator does not move through phases at all; it simply gets handed one option among several equally valid, unrelated choices — that is Strategy. When the answer to that diagnostic question is genuinely unclear, it is often a sign the underlying problem itself is a hybrid, and borrowing structural ideas from both patterns may be entirely reasonable.

10

Strategy vs. Template Method

Both deal with variable behaviour inside a fixed process. Strategy swaps in a whole approach via composition; Template Method overrides one specific step via inheritance.

Strategy is also frequently compared to the Template Method pattern, since both deal with variable behaviour tucked inside an otherwise fixed process — but they achieve that flexibility in fundamentally different ways.

PatternHow Variation HappensFlexibility
StrategyA separate object is swapped in, using compositionCan be changed at runtime, freely, by handing in a different object
Template MethodA subclass overrides one specific step, using inheritanceFixed once a subclass is chosen, decided at compile time

Template Method fixes the overall sequence of steps in a base class, letting subclasses override just one or two individual steps. Strategy, by contrast, swaps out an entire approach as a single, self-contained unit, and can do so freely while a program is already running — no subclassing required. Many experienced architects favour Strategy specifically because composition — swapping in a different object — tends to offer more flexibility than inheritance, without the rigidity of being locked into a class hierarchy decided in advance.

It is genuinely common, and often quite effective, for the two patterns to appear together within the same system, each handling a different layer of the same overall problem. A report-generation process might use Template Method to fix the overall sequence — gather data, format it, add a header, export it — while one specific step within that fixed sequence, say “format it,” delegates to a Strategy object that can be swapped between plain text, HTML, or PDF formatting. Recognising that these two patterns solve genuinely different, complementary problems, rather than competing solutions to the exact same problem, is what allows an architect to combine them confidently rather than feeling forced to pick only one.

11

Pros and Cons

A genuine trade-off. Weighing both sides honestly is what separates thoughtful use from applying it purely out of habit.

Like every pattern, Strategy is a genuine trade-off. Weighing both sides honestly is what separates thoughtful use from applying it purely out of habit.

Strengths

  • Each approach can be added, tested, and understood independently.
  • Removes long, growing conditional chains from calling code.
  • Approaches can be swapped freely while a program is running.
  • Encourages small, focused, single-purpose classes.

Trade-offs

  • Introduces extra classes, adding some upfront structure.
  • Calling code must know which strategy to choose, which is complexity itself.
  • Can feel like unnecessary ceremony when only one approach will ever exist.

The general guidance most experienced architects settle on: Strategy earns its place once a task genuinely has, or is likely to soon have, more than one interchangeable way of being accomplished. For a task with exactly one, permanently fixed approach, a single plain function remains simpler and entirely sufficient.

The trade-off worth taking most seriously is the second one listed above: Strategy does not eliminate the decision of “which approach should be used here” — it simply relocates that decision to wherever the Context gets created or configured. If that decision-making logic itself becomes messy, duplicated, or scattered across many different parts of a codebase, a team can end up with all the extra classes Strategy introduces, without actually gaining the clarity it was meant to provide. Pairing Strategy with a Factory Method, letting one dedicated piece of code decide which strategy to hand out based on context, is a common and effective way of keeping that selection logic just as clean and centralised as the strategies themselves.

Separate the “what” from the “how.” Let the “how” change freely, without the “what” ever needing to notice.
12

Common Pitfalls

Three recurring traps — over-applying it to single-approach tasks, moving complexity rather than removing it, and letting strategies quietly depend on each other.

Reaching for it when only one approach will ever exist

Wrapping a permanently single-approach task in a full Strategy hierarchy adds structure without adding real benefit — a plain function is simpler and just as flexible for that specific case.

Moving the complexity, not removing it

Strategy removes a long conditional chain from inside one function, but something still has to decide which strategy to use in the first place. If that decision logic is messy or duplicated across many places, the underlying complexity has not actually gone away — it has just moved.

Letting strategies silently depend on each other

Strategies are meant to be independent and interchangeable. If one strategy quietly relies on a specific detail of another, or on being used in a particular order, the flexibility the pattern promises has already been compromised.

!
Watch out for

A Context class riddled with checks like “if this strategy is the express one, do something slightly different here too.” That defeats the entire purpose — the Context should treat every strategy exactly the same way, without exception.

13

Frequently Asked Questions

Five honest questions that come up over and over — and honest answers to each.

Does Strategy require object-oriented programming?

The classic implementation relies on classes and a shared interface, but the underlying idea — passing in an interchangeable approach — works just as naturally in other styles of programming, often expressed by simply passing a function as a parameter instead of a class instance.

Can a strategy be changed after a Context has already been created?

Yes, and this is one of Strategy’s most valuable features — a well-designed Context typically allows its strategy to be swapped out at any point while the program is running, not just when the Context is first created.

How many strategies justify using this pattern?

There is no strict universal number, but many engineers begin seriously considering Strategy once a task has two or more genuinely distinct approaches, especially if more are likely to be added as the project grows.

Is Strategy the same as passing a callback function?

They are very closely related — a callback function is often a lightweight, function-based way of applying the same underlying idea, without the full class-based ceremony of Strategy, Interface, and ConcreteStrategy. In languages that treat functions as first-class values, this lighter approach is extremely common.

What is the difference between Strategy and simply using an if/else statement?

An if/else statement works fine for a small, stable, unlikely-to-grow number of options. Strategy becomes worthwhile once that number grows, changes often, or needs each option to be independently testable and reusable — the exact situations where a conditional chain starts to hurt.

14

Key Takeaways

Package interchangeable ways of accomplishing the same task, let calling code swap between them freely, and keep each approach small, independent, and reusable.

Remember this

  • The Strategy pattern packages interchangeable ways of accomplishing the same task, letting calling code swap between them freely.
  • It is built from three roles: Strategy (the shared interface), ConcreteStrategy (each specific approach), and Context (the code that uses whichever strategy it is given).
  • It replaces long, growing conditional chains with small, independent, individually testable classes.
  • It shows up constantly in payment processing, sorting, compression, discount rules, and route planning.
  • It has a close relationship with the Open/Closed Principle — new approaches can be added without editing existing, tested code.
  • Strategy is often confused with State (behaviour changing internally over time) and Template Method (variation through inheritance, not composition).
  • It earns its place once a task genuinely has, or is likely to grow, more than one interchangeable approach — for a fixed, single approach, a plain function remains simpler.

Leave a Reply

Your email address will not be published. Required fields are marked *