Have you ever opened a food delivery app and had to choose between “fastest route”, “cheapest option” or “fewest stops”? Or choose a payment method at checkout such as credit card, PayPal, or Wallet Balance? Behind these two, there is a good chance Strategy Patterns is at work.
The strategy pattern lets you define a family of algorithms, put each in its own class, and make them interchangeable at runtime. Instead of writing Dev if/elif Every time a behavior needs to change, you swap in the right strategy for the task.
In this tutorial, you’ll learn what the strategy pattern is, why it’s useful, and how to implement it in Python with practical examples.
You can get the code. on GitHub.
Conditions
Before we begin, make sure you have:
Python 3.10 or higher is installed.
Basic understanding of Python classes and methods
Familiarity with Object Oriented Programming (OOP) concepts
Let’s begin!
Table of Contents
What is a strategic pattern?
gave Strategy Patterns Describes a way to encapsulate a group of related algorithms so that they can be used together. The object that uses the algorithm is called Contextno need to know how it works. It simply assigns the task to whatever strategy is currently configured.
Think of it like a GPS app. The destination is the same, but you can switch between “Avoid Highways”, “Shortest Distance” or “Minimum Traffic” without changing the destination or the app. Each routing option is a separate strategy.
The pattern is useful when:
You have multiple variations of the algorithm or behavior.
You want to finish for a long time
if/elifConditional based on typeYou want to change the behavior at runtime without changing the context class.
Different parts of your app require different variations of the same operation.
Now let’s look at examples to understand this better.
Example of a simple strategy pattern
Let’s create a simple e-commerce order system where various discount strategies can be applied at checkout.
First, let’s create three discount strategies:
class RegularDiscount:
def apply(self, price):
return price * 0.95 # 5% off
class SeasonalDiscount:
def apply(self, price):
return price * 0.80 # 20% off
class NoDiscount:
def apply(self, price):
return price # no change
Each class has a single. apply A method that takes a value and returns a discounted value. He share the same interface but implement different logic.: This is the key concept in strategy pattern.
Now let’s create. Order A class that uses one of these strategies:
class Order:
def __init__(self, product, price, discount_strategy):
self.product = product
self.price = price
self.discount_strategy = discount_strategy
def final_price(self):
return self.discount_strategy.apply(self.price)
def summary(self):
print(f"Product : {self.product}")
print(f"Original: ${self.price:.2f}")
print(f"Final : ${self.final_price():.2f}")
print("-" * 30)
gave Order The class is ours Context. It has no deductive logic in itself – it assigns it entirely. discount_strategy.apply(). Whatever strategy you object to, it works.
Now let’s order something:
order1 = Order("Mechanical Keyboard", 120.00, NoDiscount())
order2 = Order("Laptop Stand", 45.00, RegularDiscount())
order3 = Order("USB-C Hub", 35.00, SeasonalDiscount())
order1.summary()
order2.summary()
order3.summary()
Running the above code should give you the following output:
Product : Mechanical Keyboard
Original: $120.00
Final : $120.00
------------------------------
Product : Laptop Stand
Original: $45.00
Final : $42.75
------------------------------
Product : USB-C Hub
Original: $35.00
Final : $28.00
------------------------------
Note how Order Never checks if discount_type == "seasonal". It just calls. apply() and relies on a strategy to handle it. Adding a new discount type in the future means creating a new class and changing nothing.
Changing strategies at runtime
The main advantage of the strategy pattern is that you can change the strategy while the program is running. Let’s say a user upgrades to premium membership mid-session:
class ShoppingCart:
def __init__(self):
self.items = ()
self.discount_strategy = NoDiscount() # default
def add_item(self, name, price):
self.items.append({"name": name, "price": price})
def set_discount(self, strategy):
self.discount_strategy = strategy
print(f"Discount updated to: {strategy.__class__.__name__}")
def checkout(self):
print("\n--- Checkout Summary ---")
total = 0
for item in self.items:
discounted = self.discount_strategy.apply(item("price"))
print(f"{item('name')}: ${discounted:.2f}")
total += discounted
print(f"Total: ${total:.2f}\n")
gave set_discount The method allows us to change the strategy at any point. Let’s see it in action:
cart = ShoppingCart()
cart.add_item("Notebook", 15.00)
cart.add_item("Desk Lamp", 40.00)
cart.add_item("Monitor Riser", 25.00)
# Checkout as a regular customer
cart.checkout()
# User upgrades to seasonal sale membership
cart.set_discount(SeasonalDiscount())
cart.checkout()
These results:
--- Checkout Summary ---
Notebook: $15.00
Desk Lamp: $40.00
Monitor Riser: $25.00
Total: $80.00
Discount updated to: SeasonalDiscount
--- Checkout Summary ---
Notebook: $12.00
Desk Lamp: $32.00
Monitor Riser: $20.00
Total: $64.00
The basket itself hasn’t changed – only the strategy has. This is a benefit to have. behavior separate from Context who uses it.
Using abstract base classes
Yet, nothing enforces that every strategy has one. apply Method If someone makes a strategy and forgets it, they will get a secret information. AttributeError At runtime we can stop using it. Python’s abstract base classes.
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, price: float) -> float:
pass
Now let’s rewrite our strategies to inherit from it:
class RegularDiscount(DiscountStrategy):
def apply(self, price):
return price * 0.95
class SeasonalDiscount(DiscountStrategy):
def apply(self, price):
return price * 0.80
class NoDiscount(DiscountStrategy):
def apply(self, price):
return price
Now if one makes a broken strategy. applyPython will pick one up TypeError Immediately when they try to speed it up – before any code runs. This is pretty much a cleaner failure.
class BrokenStrategy(DiscountStrategy):
pass # forgot to implement apply()
s = BrokenStrategy() # raises TypeError right here
Using ABCs is especially helpful in large teams or shared codebases, where you want to clarify the agreement: each strategy. It is necessary apply apply. Otherwise, you get an error as shown.
2 pass # forgot to implement apply()
3
----> 4 s = BrokenStrategy() # raises TypeError right here
TypeError: Can't instantiate abstract class BrokenStrategy without an implementation for abstract method 'apply'
When to Use a Strategy Pattern
A strategic model is appropriate when:
You have branching logic based on type – long
if/elifBlocks that check the “mode” or “type” variables are an indication that the strategy can help.Behavior needs to be changed at runtime – when users or config values ​​need to be able to change the algorithm without restarting.
You’re building an extensible system — new behaviors can be added as new classes without touching existing code.
You want to test the algorithm independently — each strategy is its own class, which makes unit testing straightforward.
Avoid it when:
You have only two variables that will never increase – a simple one
if/elseIt’s all right there.Strategies have so much state that separating them into classes adds complexity without benefit.
The result
I hope you found this tutorial useful. In summary, the Strategy pattern gives you a neat way to manage different behavior without polluting your classes with conditional logic. Context remains simple and stable and strategy handles complexity.
We covered the basic pattern, changing runtime strategies, and implementing contracts with abstract base classes. As with most design patterns, start simple: even without ABCs, separating your algorithms into their own classes makes your code easier to read, test, and extend.
Happy coding!