Strategy Pattern: Switching Algorithms at Runtime
So far in this series, we’ve explored the Singleton and Factory patterns—two of the most used creational patterns in backend development. In today’s post, we’re shifting gears to a behavioral pattern that’s all about flexibility in how something is done: the Strategy Pattern.
If you've ever written code with
What Is the Strategy Pattern?
The Strategy Pattern defines a family of algorithms or behaviors, encapsulates each one, and makes them interchangeable at runtime. Instead of hardcoding logic in
It’s about delegating behavior to a separate class—so that new behaviors can be added without modifying existing code.
Real-World Analogy: Payment Strategy in a Marketplace
Imagine a marketplace app where customers can pay using credit card, PayPal, or e-wallet. If you put all that logic in one massive method, it’ll become hard to maintain.
Instead, you could define a separate payment strategy for each method and plug in the right one when needed.
The system doesn’t need to know how each payment method works internally. It just needs to know that the chosen strategy knows how to pay.
Real Backend Example: Dynamic Discount Strategies in Python
Let’s say you’re building an e-commerce backend in Python, and different products or users are eligible for different discount strategies—like flat discount, percentage-based, or even dynamic discounts based on inventory or date.
Step 1: Define the Strategy Interface
from abc import ABC, abstractmethod class DiscountStrategy(ABC): @abstractmethod def apply_discount(self, price: float) -> float: pass
Step 2: Create Concrete Strategies
class NoDiscount(DiscountStrategy): def apply_discount(self, price: float) -> float: return price class PercentageDiscount(DiscountStrategy): def __init__(self, percentage: float): self.percentage = percentage def apply_discount(self, price: float) -> float: return price * (1 - self.percentage / 100) class FlatDiscount(DiscountStrategy): def __init__(self, amount: float): self.amount = amount def apply_discount(self, price: float) -> float: return max(0, price - self.amount)
Step 3: Use the Strategy in a Context Class
class PricingEngine: def __init__(self, strategy: DiscountStrategy): self.strategy = strategy def calculate_price(self, price: float) -> float: return self.strategy.apply_discount(price)
Usage
# main.py # For a loyal customer strategy = PercentageDiscount(10) engine = PricingEngine(strategy) print(engine.calculate_price(100)) # Outputs 90.0 # For clearance item strategy = FlatDiscount(20) engine = PricingEngine(strategy) print(engine.calculate_price(100)) # Outputs 80.0 # No discount strategy = NoDiscount() engine = PricingEngine(strategy) print(engine.calculate_price(100)) # Outputs 100.0
This setup allows you to plug in any discount logic, even ones based on more complex business rules like inventory level, time of day, or customer type—without changing the core engine.
Where Strategy Pattern Shines in Backend Systems
- Choosing between different authentication methods (e.g., OAuth2, JWT, API Key)
- Applying rate limiting strategies (e.g., fixed window, sliding window, token bucket)
- Handling dynamic pricing rules
- Performing custom validation rules per client or tenant
- Processing different file upload types or parsers
Gotchas
- Don’t use Strategy Pattern when behavior is unlikely to change or there are only two trivial options.
- Watch for strategy “explosion”—too many tiny strategy classes can become difficult to manage without grouping or using a registry.
Final Thoughts
The Strategy Pattern is one of the cleanest ways to achieve behavioral flexibility. It adheres to the Open/Closed Principle: your system is open to extension (via new strategies) but closed to modification.
It keeps your codebase modular and readable, and when paired with Dependency Injection or configuration-based logic, it becomes incredibly powerful for scalable backend systems.
Coming up next in this design pattern series: the Decorator Pattern — perfect for extending behavior without altering existing code.