You start simple.
A small feature. A couple of conditions.
Then it grows.
And suddenly, your code looks like this:
def process_payment(method, amount):
if method == "credit_card":
return f"Processing {amount} via Credit Card"
elif method == "paypal":
return f"Processing {amount} via PayPal"
elif method == "upi":
return f"Processing {amount} via UPI"
elif method == "crypto":
return f"Processing {amount} via Crypto"
else:
raise ValueError("Unsupported payment method")
Looks fine… until it doesn’t.
The Problem
At first glance, this works.
But the real issue isn’t the if-else.
It’s this:
Every new behavior forces you to modify existing logic.
And that has consequences:
- Every new payment method → changes a critical function
- Higher risk of breaking existing flows
- More regression testing
- Tighter coupling between teams
- Slower feature delivery
This isn’t just a code smell.
It’s an architecture problem.
Why This Breaks in Real Systems
Now imagine:
- You’re building a FastAPI service
- Multiple teams add payment providers
- Each provider has different validation, retries, logging
Now this one function becomes:
- a hotspot for changes
- a merge conflict zone
- a source of production bugs
The problem is no longer syntax.
It’s change amplification.
What We Actually Want
We don’t want:
“Pick a method and run logic inside one function”
We want:
“Pick a behavior and delegate execution to it”
Even more precisely:
Separate what to do from how it’s done
That’s the Strategy Pattern.
The Idea
Instead of writing all logic in one place:
- You create separate “strategies”
- Each handles one behavior
- You select the strategy at runtime
At a deeper level:
We’re programming to a contract, not a concrete implementation.
Refactoring with Strategy Pattern
Step 1: Define a common contract
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
Step 2: Create concrete strategies
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
return f"Processing {amount} via Credit Card"
class PayPalPayment(PaymentStrategy):
def pay(self, amount):
return f"Processing {amount} via PayPal"
class UPIPayment(PaymentStrategy):
def pay(self, amount):
return f"Processing {amount} via UPI"
Step 3: Execute without conditionals
def process_payment(strategy: PaymentStrategy, amount):
return strategy.pay(amount)
Selecting the Right Strategy (Real Systems)
STRATEGY_MAP = {
"credit_card": CreditCardPayment(),
"paypal": PayPalPayment(),
"upi": UPIPayment(),
}
def get_strategy(method: str) -> PaymentStrategy:
try:
return STRATEGY_MAP[method]
except KeyError:
raise ValueError("Unsupported payment method")
Real-World Note: Strategies Aren’t Always Stateless
class StripePayment(PaymentStrategy):
def __init__(self, client):
self.client = client
def pay(self, amount):
return self.client.charge(amount)
FastAPI Example
from fastapi import FastAPI
app = FastAPI()
@app.post("/pay")
def pay(method: str, amount: int):
strategy = get_strategy(method)
return process_payment(strategy, amount)
Mental Model
Separate what to do from how it’s done, and make behavior replaceable at runtime.
