Today, your e-commerce app's Order.pay() method handles UPI and Card payments with a simple if/else. It works fine.
Then the interviewer smiles and says: "Good. Now add Net Banking."
Where would you make that change? If your honest answer is "I'd add another elif inside Order.pay()," you've just found the exact pattern this lesson is about — before we've even named it.
The pain: a growing if/else
class Order:
def pay(self, method, amount):
if method == "upi":
# UPI payment logic
...
elif method == "card":
# Card payment logic
...
elif method == "netbanking": # every new method = another elif
...Every new payment method means editing Order.pay() — code that's already tested and already working. Six months from now, this method could easily have wallets, EMI, and Buy-Now-Pay-Later all crammed into one growing conditional.
The pattern: pull the algorithm out
Define one interface for "a way to pay," and give each payment method its own class:
class PaymentStrategy:
def pay(self, amount): raise NotImplementedError
class UPIPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid ₹{amount} via UPI")
class CardPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid ₹{amount} via Card")
class NetBankingPayment(PaymentStrategy): # the new requirement
def pay(self, amount):
print(f"Paid ₹{amount} via Net Banking")Structure: Context holds a Strategy
Order becomes the Context — it holds a reference to some PaymentStrategy, without knowing or caring which concrete one it is:
class Order:
def __init__(self, payment_strategy: PaymentStrategy):
self.payment_strategy = payment_strategy # the abstraction, not a concrete class
def pay(self, amount):
self.payment_strategy.pay(amount) # delegates — Order never changes again
# usage:
order = Order(UPIPayment())
order.pay(500)
order.payment_strategy = NetBankingPayment() # swapped at runtime
order.pay(1200)Adding EMI later is just one more class implementing PaymentStrategy — Order.pay() is never touched again. RefactoringGuru's definition captures this exactly: Strategy "lets you define a family of algorithms, put each one into a separate class, and make their objects interchangeable."
A second example: pricing
The same shape shows up constantly. A parking lot (you'll design the whole system a few lessons from now) has the identical pressure for pricing:
class PricingStrategy:
def calculate_fee(self, ticket): raise NotImplementedError
class HourlyPricingStrategy(PricingStrategy):
def calculate_fee(self, ticket):
return ticket.duration_hours() * 20
class FlatRateStrategy(PricingStrategy):
def calculate_fee(self, ticket):
return 50Same reasoning, different domain — this is what "recognising a pattern in a fresh problem" actually looks like: not the word "pricing" or "payment," but the shape "interchangeable algorithm, chosen per use, likely to grow new variants."
Why this satisfies OCP and DIP
- Open/Closed: new payment methods are added as new classes;
Orderis never modified. - Dependency Inversion:
Order(high-level) depends onPaymentStrategy(an abstraction), never onUPIPaymentorCardPaymentdirectly.
This is exactly the connection promised at the end of the SOLID lesson: Strategy isn't a separate idea from SOLID — it's SOLID, given a name and a reusable shape.
Strategy vs State (a quick preview)
Structurally, Strategy and State look almost identical — both put interchangeable behaviour behind an interface. The difference is who chooses, and why it changes: a payment Strategy is chosen once by whoever places the order. An elevator's State (idle, moving up, door open) is the elevator's own internal condition, and the elevator itself switches states in response to events. You'll see State properly in the Elevator System and Vending Machine lessons later in this course — for now, just notice the shape is familiar.
When Strategy is overkill
TaxStrategy interface for one tax rule fixed by law, with no realistic second variant. If there's truly one algorithm and no sign of a second, a plain method is simpler and just as correct — Strategy is a tool for interchangeable algorithms, not a default for every calculation in your codebase.Common mistakes
- Naming the pattern before explaining the pain (see Lesson 1) — always derive it from a growing conditional or a stated need for interchangeable behaviour.
- Making the Context depend on a concrete strategy class instead of the interface (quietly breaking DIP again).
- Introducing Strategy for a single, stable algorithm with no real variation.
- Confusing Strategy (client-chosen behaviour) with State (the object's own internal condition) — covered properly in the Elevator and Vending Machine lessons.
Quick recap
| Idea | One-liner |
|---|---|
| Pain solved | A growing if/else choosing between interchangeable algorithms. |
| Structure | A Strategy interface + one class per algorithm; Context holds the interface, not a concrete class. |
| SOLID link | Open/Closed (new algorithm = new class) + Dependency Inversion (Context depends on the abstraction). |
| vs State | Strategy = chosen by the client; State = the object's own internal condition. |
| Overkill case | One fixed algorithm, no real variation expected. |
One thing to remember
If you can name two or more ways to do the same job, and the choice can change per use, that behaviour belongs behind a Strategy interface — not inside an if/else.
Practice Zone
Six MCQs, then three reasoning questions.
What problem does the Strategy pattern solve?
Asked in


In the Strategy pattern, what does the 'Context' class hold?
Asked in

Which two SOLID principles does the Strategy pattern most directly support?
Asked in

Strategy and State look structurally similar (both swap out behaviour behind an interface). What's the key conceptual difference?
Asked in

Which requirement phrase is the strongest Strategy signal?
Asked in


A TaxCalculator has exactly one tax rule, fixed by law, with zero realistic variants expected. Should you build a TaxStrategy interface for it?
Asked in

Think it through, then reveal:
Today an e-commerce Order supports UPI and Card payments, hard-coded as if method == 'upi': ... elif method == 'card': ... inside Order.pay(). The interviewer says: "Now add Net Banking." Where would you make the change, and why does that reveal a design problem?
Asked in


Why is Strategy specifically useful for swapping behaviour at runtime, not just at compile time?
Asked in

What's the actual cost of introducing a Strategy pattern, and when does that cost outweigh the benefit?
Asked in

FAQ
Isn't Strategy just passing a function instead of a class?
In languages with first-class functions, a plain function (or lambda) can absolutely play the same role as a tiny Strategy class — and for a very simple case, that's often cleaner. Strategy-as-a-class earns its place when the algorithm needs its own state, its own constructor parameters, or when the interface has more than one method.
How many Strategy implementations is 'too many'?
There's no fixed number — the question is whether each one still represents a genuinely distinct algorithm serving the same interface. If you find yourself creating a strategy class for every tiny variation instead of parameterising one class, that's a sign to consolidate rather than multiply classes further.
Can a Context use more than one Strategy at a time?
Yes — nothing stops a class from holding several strategy fields (say, a PricingStrategy and a DiscountStrategy separately). Each addresses a different axis of variation, and keeping them separate is itself good Single Responsibility practice.
Strategy swapped out what an object does. Next, we look at who gets told when something happens — Lesson 6: Observer Pattern →


