Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Strategy Pattern

Swap algorithms on the fly - define a family of algorithms, encapsulate each one, and make them interchangeable!

Strategy Pattern: Swapping Algorithms at Runtime

Section titled “Strategy Pattern: Swapping Algorithms at Runtime”

Now let’s dive into the Strategy Pattern - one of the most practical behavioral design patterns that enables you to define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.

Imagine you’re using a GPS navigation app. You can choose different routes - fastest, shortest, avoid tolls, scenic route. Each routing algorithm is different, but they all solve the same problem: getting you from A to B. The Strategy Pattern works the same way!

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it, enabling you to swap behaviors at runtime without changing the client code.

The Strategy Pattern is useful when:

  1. You have multiple algorithms for a specific task and want to switch between them
  2. You want to avoid conditionals - No more if/else or switch statements for algorithm selection
  3. You need runtime flexibility - Change algorithm without modifying client code
  4. You want to isolate algorithm code - Each strategy is in its own class
  5. You need to test algorithms independently - Easy to unit test each strategy

What Happens If We Don’t Use Strategy Pattern?

Section titled “What Happens If We Don’t Use Strategy Pattern?”

Without the Strategy Pattern, you might:

  • Use massive if/else chains - Hard to maintain and extend
  • Violate Open/Closed Principle - Need to modify code to add new algorithms
  • Duplicate code - Similar algorithms with slight variations
  • Tight coupling - Client knows about all algorithm implementations
  • Hard to test - Algorithms mixed with business logic

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Strategy Pattern works in practice - showing how strategies are swapped at runtime:

sequenceDiagram
    participant Client
    participant Context as SortingApplication
    participant Strategy1 as QuickSort
    participant Strategy2 as MergeSort
    
    Client->>Context: set_strategy(QuickSort)
    activate Context
    Context->>Context: Store strategy reference
    Context-->>Client: Strategy set
    deactivate Context
    
    Client->>Context: sort([3, 1, 4, 1, 5])
    activate Context
    Context->>Strategy1: sort([3, 1, 4, 1, 5])
    activate Strategy1
    Strategy1->>Strategy1: Execute QuickSort algorithm
    Strategy1-->>Context: [1, 1, 3, 4, 5]
    deactivate Strategy1
    Context-->>Client: [1, 1, 3, 4, 5]
    deactivate Context
    
    Note over Client,Strategy2: Client can swap strategy at runtime!
    
    Client->>Context: set_strategy(MergeSort)
    Context-->>Client: Strategy changed
    
    Client->>Context: sort([9, 7, 5, 3, 1])
    activate Context
    Context->>Strategy2: sort([9, 7, 5, 3, 1])
    activate Strategy2
    Strategy2->>Strategy2: Execute MergeSort algorithm
    Strategy2-->>Context: [1, 3, 5, 7, 9]
    deactivate Strategy2
    Context-->>Client: [1, 3, 5, 7, 9]
    deactivate Context

You’re building a sorting utility that needs to support multiple sorting algorithms. Without Strategy Pattern:

bad_sorting.py
# ❌ Without Strategy Pattern - Massive if/else chain!
from typing import List
class SortingApplication:
def __init__(self):
self.algorithm = "quicksort" # Default algorithm
def set_algorithm(self, algorithm: str):
self.algorithm = algorithm
def sort(self, data: List[int]) -> List[int]:
# Problem: Massive if/else chain!
if self.algorithm == "bubble":
# Bubble sort implementation
result = data.copy()
n = len(result)
for i in range(n):
for j in range(0, n - i - 1):
if result[j] > result[j + 1]:
result[j], result[j + 1] = result[j + 1], result[j]
return result
elif self.algorithm == "quick":
# Quick sort implementation
def quicksort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)
return quicksort(data)
elif self.algorithm == "merge":
# Merge sort implementation
def mergesort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = mergesort(arr[:mid])
right = mergesort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
return mergesort(data)
else:
raise ValueError(f"Unknown algorithm: {self.algorithm}")
# Problems:
# - Adding new algorithm requires modifying this class
# - Violates Open/Closed Principle
# - Hard to test individual algorithms
# - Code is hard to read and maintain
# Usage
app = SortingApplication()
app.set_algorithm("bubble")
print(app.sort([3, 1, 4, 1, 5]))

Problems:

  • Massive if/else chain - Hard to read and maintain
  • Violates Open/Closed Principle - Need to modify class to add algorithms
  • Hard to test - All algorithms in one class
  • Tight coupling - Client knows about algorithm details
classDiagram
    class SortStrategy {
        <<interface>>
        +sort(data) List
    }
    class BubbleSortStrategy {
        +sort(data) List
    }
    class QuickSortStrategy {
        +sort(data) List
    }
    class MergeSortStrategy {
        +sort(data) List
    }
    class SortingApplication {
        -strategy: SortStrategy
        +set_strategy(strategy) void
        +sort(data) List
    }
    
    SortStrategy <|.. BubbleSortStrategy : implements
    SortStrategy <|.. QuickSortStrategy : implements
    SortStrategy <|.. MergeSortStrategy : implements
    SortingApplication --> SortStrategy : uses
    
    note for SortStrategy "All strategies implement\nthe same interface"
    note for SortingApplication "Context delegates to\ncurrent strategy"
strategy_sorting.py
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define the Strategy interface
class SortStrategy(ABC):
"""Strategy interface for sorting algorithms"""
@abstractmethod
def sort(self, data: List[int]) -> List[int]:
"""Sort the data and return sorted list"""
pass
# Step 2: Implement Concrete Strategies
class BubbleSortStrategy(SortStrategy):
"""Bubble sort strategy - O(n²) but simple"""
def sort(self, data: List[int]) -> List[int]:
result = data.copy()
n = len(result)
for i in range(n):
for j in range(0, n - i - 1):
if result[j] > result[j + 1]:
result[j], result[j + 1] = result[j + 1], result[j]
print("📊 Using Bubble Sort (O(n²) - good for small datasets)")
return result
class QuickSortStrategy(SortStrategy):
"""Quick sort strategy - O(n log n) average"""
def sort(self, data: List[int]) -> List[int]:
def quicksort(arr: List[int]) -> List[int]:
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)
print("⚡ Using Quick Sort (O(n log n) - fast for most cases)")
return quicksort(data)
class MergeSortStrategy(SortStrategy):
"""Merge sort strategy - O(n log n) guaranteed"""
def sort(self, data: List[int]) -> List[int]:
def mergesort(arr: List[int]) -> List[int]:
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = mergesort(arr[:mid])
right = mergesort(arr[mid:])
return self._merge(left, right)
print("🔀 Using Merge Sort (O(n log n) - stable, predictable)")
return mergesort(data)
def _merge(self, left: List[int], right: List[int]) -> List[int]:
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# Step 3: Create the Context class
class SortingApplication:
"""Context class that uses sorting strategies"""
def __init__(self, strategy: SortStrategy = None):
self._strategy = strategy or QuickSortStrategy() # Default strategy
def set_strategy(self, strategy: SortStrategy) -> None:
"""Change the sorting strategy at runtime"""
self._strategy = strategy
print(f"✅ Strategy changed to: {strategy.__class__.__name__}")
def sort(self, data: List[int]) -> List[int]:
"""Sort data using the current strategy"""
print(f"\n🔄 Sorting {data}")
result = self._strategy.sort(data)
print(f"✨ Result: {result}")
return result
# Step 4: Use the pattern
def main():
# Create context with default strategy
app = SortingApplication()
data = [64, 34, 25, 12, 22, 11, 90]
# Sort with default (QuickSort)
app.sort(data)
# Switch to BubbleSort
app.set_strategy(BubbleSortStrategy())
app.sort(data)
# Switch to MergeSort
app.set_strategy(MergeSortStrategy())
app.sort(data)
print("\n✅ Strategy Pattern: Algorithms swapped at runtime!")
if __name__ == "__main__":
main()

Real-World Software Example: Payment Processing System

Section titled “Real-World Software Example: Payment Processing System”

Now let’s see a realistic software example - a payment processing system that supports multiple payment methods.

You’re building an e-commerce checkout system that needs to support multiple payment methods - Credit Card, PayPal, and Cryptocurrency. Without Strategy Pattern:

bad_payment.py
# ❌ Without Strategy Pattern - if/else nightmare!
class PaymentProcessor:
def __init__(self):
self.payment_method = "credit_card"
def set_payment_method(self, method: str):
self.payment_method = method
def process_payment(self, amount: float, details: dict) -> bool:
# Problem: Massive if/else chain!
if self.payment_method == "credit_card":
card_number = details.get("card_number")
cvv = details.get("cvv")
expiry = details.get("expiry")
# Validate card
if not card_number or len(card_number) != 16:
raise ValueError("Invalid card number")
if not cvv or len(cvv) != 3:
raise ValueError("Invalid CVV")
# Process credit card payment
print(f"💳 Processing credit card payment of ${amount}")
print(f" Card: **** **** **** {card_number[-4:]}")
# Connect to payment gateway...
return True
elif self.payment_method == "paypal":
email = details.get("email")
# Validate PayPal
if not email or "@" not in email:
raise ValueError("Invalid PayPal email")
# Process PayPal payment
print(f"🅿️ Processing PayPal payment of ${amount}")
print(f" Email: {email}")
# Redirect to PayPal...
return True
elif self.payment_method == "crypto":
wallet_address = details.get("wallet_address")
currency = details.get("currency", "BTC")
# Validate crypto
if not wallet_address or len(wallet_address) < 26:
raise ValueError("Invalid wallet address")
# Process crypto payment
print(f"₿ Processing {currency} payment of ${amount}")
print(f" Wallet: {wallet_address[:10]}...")
# Generate crypto invoice...
return True
else:
raise ValueError(f"Unknown payment method: {self.payment_method}")
# Problems:
# - Adding new payment method requires modifying this class
# - Each payment method has different validation logic
# - Hard to test individual payment methods
# - Violates Single Responsibility Principle
# Usage
processor = PaymentProcessor()
processor.set_payment_method("credit_card")
processor.process_payment(99.99, {"card_number": "1234567890123456", "cvv": "123", "expiry": "12/25"})

Problems:

  • Adding new payment methods requires modifying the processor class
  • Different validation logic mixed in one class
  • Hard to test individual payment methods
  • Violates Single Responsibility and Open/Closed principles
classDiagram
    class PaymentStrategy {
        <<interface>>
        +validate(details) bool
        +process(amount, details) PaymentResult
    }
    class CreditCardStrategy {
        +validate(details) bool
        +process(amount, details) PaymentResult
    }
    class PayPalStrategy {
        +validate(details) bool
        +process(amount, details) PaymentResult
    }
    class CryptoStrategy {
        +validate(details) bool
        +process(amount, details) PaymentResult
    }
    class PaymentProcessor {
        -strategy: PaymentStrategy
        +set_strategy(strategy) void
        +process_payment(amount, details) PaymentResult
    }
    
    PaymentStrategy <|.. CreditCardStrategy : implements
    PaymentStrategy <|.. PayPalStrategy : implements
    PaymentStrategy <|.. CryptoStrategy : implements
    PaymentProcessor --> PaymentStrategy : uses
    
    note for PaymentStrategy "Each payment method\nis a separate strategy"
    note for PaymentProcessor "Delegates to current\npayment strategy"
strategy_payment.py
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, Any
from enum import Enum
# Step 1: Define result classes
class PaymentStatus(Enum):
SUCCESS = "success"
FAILED = "failed"
PENDING = "pending"
@dataclass
class PaymentResult:
"""Result of a payment operation"""
status: PaymentStatus
transaction_id: str
message: str
# Step 2: Define the Strategy interface
class PaymentStrategy(ABC):
"""Strategy interface for payment methods"""
@abstractmethod
def validate(self, details: Dict[str, Any]) -> bool:
"""Validate payment details"""
pass
@abstractmethod
def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult:
"""Process the payment"""
pass
# Step 3: Implement Concrete Strategies
class CreditCardStrategy(PaymentStrategy):
"""Credit card payment strategy"""
def validate(self, details: Dict[str, Any]) -> bool:
card_number = details.get("card_number", "")
cvv = details.get("cvv", "")
expiry = details.get("expiry", "")
if len(card_number) != 16 or not card_number.isdigit():
raise ValueError("Invalid card number - must be 16 digits")
if len(cvv) != 3 or not cvv.isdigit():
raise ValueError("Invalid CVV - must be 3 digits")
if not expiry or "/" not in expiry:
raise ValueError("Invalid expiry date - use MM/YY format")
return True
def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult:
self.validate(details)
card_number = details["card_number"]
print(f"💳 Processing credit card payment")
print(f" Amount: ${amount:.2f}")
print(f" Card: **** **** **** {card_number[-4:]}")
print(f" Connecting to payment gateway...")
# Simulate payment processing
transaction_id = f"CC-{card_number[-4:]}-{int(amount * 100)}"
return PaymentResult(
status=PaymentStatus.SUCCESS,
transaction_id=transaction_id,
message="Credit card payment successful"
)
class PayPalStrategy(PaymentStrategy):
"""PayPal payment strategy"""
def validate(self, details: Dict[str, Any]) -> bool:
email = details.get("email", "")
if not email or "@" not in email:
raise ValueError("Invalid PayPal email address")
return True
def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult:
self.validate(details)
email = details["email"]
print(f"🅿️ Processing PayPal payment")
print(f" Amount: ${amount:.2f}")
print(f" Email: {email}")
print(f" Redirecting to PayPal...")
# Simulate payment processing
transaction_id = f"PP-{email.split('@')[0]}-{int(amount * 100)}"
return PaymentResult(
status=PaymentStatus.SUCCESS,
transaction_id=transaction_id,
message="PayPal payment successful"
)
class CryptoStrategy(PaymentStrategy):
"""Cryptocurrency payment strategy"""
def validate(self, details: Dict[str, Any]) -> bool:
wallet_address = details.get("wallet_address", "")
if len(wallet_address) < 26:
raise ValueError("Invalid wallet address - too short")
return True
def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult:
self.validate(details)
wallet_address = details["wallet_address"]
currency = details.get("currency", "BTC")
print(f"₿ Processing {currency} payment")
print(f" Amount: ${amount:.2f}")
print(f" Wallet: {wallet_address[:10]}...{wallet_address[-4:]}")
print(f" Generating invoice...")
# Simulate payment processing
transaction_id = f"CRYPTO-{currency}-{int(amount * 100)}"
return PaymentResult(
status=PaymentStatus.PENDING,
transaction_id=transaction_id,
message=f"Awaiting {currency} confirmation"
)
# Step 4: Create the Context class
class PaymentProcessor:
"""Context class that uses payment strategies"""
def __init__(self, strategy: PaymentStrategy = None):
self._strategy = strategy or CreditCardStrategy() # Default
def set_strategy(self, strategy: PaymentStrategy) -> None:
"""Change payment strategy at runtime"""
self._strategy = strategy
print(f"\n✅ Payment method changed to: {strategy.__class__.__name__}")
def process_payment(self, amount: float, details: Dict[str, Any]) -> PaymentResult:
"""Process payment using current strategy"""
print(f"\n{'='*50}")
print(f"Processing payment of ${amount:.2f}")
print(f"{'='*50}")
result = self._strategy.process(amount, details)
print(f"\n📋 Result: {result.status.value}")
print(f"📋 Transaction ID: {result.transaction_id}")
print(f"📋 Message: {result.message}")
return result
# Step 5: Use the pattern
def main():
# Create payment processor
processor = PaymentProcessor()
# Process credit card payment
processor.process_payment(99.99, {
"card_number": "4532015112830366",
"cvv": "123",
"expiry": "12/25"
})
# Switch to PayPal
processor.set_strategy(PayPalStrategy())
processor.process_payment(49.99, {
"email": "[email protected]"
})
# Switch to Crypto
processor.set_strategy(CryptoStrategy())
processor.process_payment(199.99, {
"wallet_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"currency": "BTC"
})
# Easy to add new payment method - just create new strategy!
print("\n\n✅ Strategy Pattern: Payment methods swapped at runtime!")
print("✅ Adding new payment method = Just create a new strategy class!")
if __name__ == "__main__":
main()

There are different ways to implement the Strategy Pattern:

Using abstract classes/interfaces:

classic_strategy.py
# Classic Strategy - class-based
from abc import ABC, abstractmethod
class Strategy(ABC):
@abstractmethod
def execute(self, data): pass
class ConcreteStrategyA(Strategy):
def execute(self, data):
return f"Strategy A: {data}"
class Context:
def __init__(self, strategy: Strategy):
self._strategy = strategy
def do_something(self, data):
return self._strategy.execute(data)

Pros: Type-safe, clear contracts, easy to extend
Cons: More classes to manage

Using functions/lambdas:

functional_strategy.py
# Functional Strategy - using functions
from typing import Callable
class Context:
def __init__(self, strategy: Callable):
self._strategy = strategy
def do_something(self, data):
return self._strategy(data)
# Use lambda or function as strategy
context = Context(lambda data: f"Strategy A: {data}")
print(context.do_something("Hello"))
# Change strategy
context._strategy = lambda data: f"Strategy B: {data.upper()}"
print(context.do_something("Hello"))

Pros: Less boilerplate, more flexible
Cons: Less type-safe, harder to document

Strategies that can be configured:

configurable_strategy.py
# Strategy with configuration
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, data: bytes) -> bytes: pass
class GzipStrategy(CompressionStrategy):
def __init__(self, level: int = 6):
self.level = level # Compression level 1-9
def compress(self, data: bytes) -> bytes:
import gzip
return gzip.compress(data, compresslevel=self.level)
# Usage - strategy with different configurations
fast_compression = GzipStrategy(level=1)
max_compression = GzipStrategy(level=9)

Use Strategy Pattern when:

You have multiple algorithms - Different ways to do the same thing
You need runtime switching - Change algorithm based on user input or conditions
You want to eliminate conditionals - Replace if/else or switch statements
Algorithms should be interchangeable - Same interface, different implementations
You need to isolate algorithm code - Each algorithm in its own class

Don’t use Strategy Pattern when:

Only one algorithm exists - No need to abstract
Algorithm never changes - Static behavior is simpler
Simple conditionals - 2-3 branches might be clearer without pattern
Performance is critical - Indirection has small overhead
Over-engineering - Don’t add complexity for hypothetical future needs


shared_state.py
# ❌ Bad: Strategy with shared mutable state
class BadStrategy:
shared_cache = {} # Class-level shared state!
def execute(self, data):
self.shared_cache[data] = result # Bad: Mutating shared state
return result
# ✅ Good: Strategy without shared state
class GoodStrategy:
def __init__(self):
self._cache = {} # Instance-level state
def execute(self, data):
if data not in self._cache:
self._cache[data] = self._compute(data)
return self._cache[data]

Mistake 2: Context Exposing Strategy Details

Section titled “Mistake 2: Context Exposing Strategy Details”
exposing_strategy.py
# ❌ Bad: Context exposes strategy internals
class BadContext:
def __init__(self, strategy):
self.strategy = strategy # Public access!
def get_strategy_name(self): # Bad: Exposing strategy details
return self.strategy.__class__.__name__
# ✅ Good: Context hides strategy details
class GoodContext:
def __init__(self, strategy):
self._strategy = strategy # Private
def execute(self, data):
return self._strategy.execute(data)
# No methods exposing strategy internals
strategy_knows_context.py
# ❌ Bad: Strategy depends on Context
class BadStrategy:
def __init__(self, context): # Bad: Strategy knows about context
self.context = context
def execute(self, data):
return self.context.some_method() # Bad: Tight coupling!
# ✅ Good: Strategy is independent
class GoodStrategy:
def execute(self, data, helper_func=None):
# Strategy doesn't know about context
# If needed, pass data through parameters
if helper_func:
return helper_func(data)
return self._process(data)

  1. Open/Closed Principle - Add new algorithms without modifying existing code
  2. Single Responsibility - Each algorithm in its own class
  3. Runtime Flexibility - Change algorithms dynamically
  4. Eliminates Conditionals - No more if/else chains
  5. Easy Testing - Test each strategy independently
  6. Code Reuse - Strategies can be reused across contexts

Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.

  • Multiple algorithms - Different ways to accomplish same task
  • Runtime switching - Change algorithm based on conditions
  • Eliminate conditionals - No if/else chains
  • Easy testing - Test each strategy independently
  • Follow Open/Closed Principle
  1. Define Strategy interface - Common interface for all algorithms
  2. Create Concrete Strategies - Each algorithm in its own class
  3. Create Context - Uses a strategy through the interface
  4. Set Strategy - Context can change strategy at runtime
  5. Execute - Context delegates to current strategy
Context → Strategy Interface → Concrete Strategies
  • Strategy - Interface for all algorithms
  • Concrete Strategy - Specific algorithm implementation
  • Context - Uses a strategy, allows switching
  • Client - Configures context with strategy
class Strategy(ABC):
@abstractmethod
def execute(self, data): pass
class ConcreteStrategy(Strategy):
def execute(self, data):
return process(data)
class Context:
def __init__(self, strategy: Strategy):
self._strategy = strategy
def set_strategy(self, strategy: Strategy):
self._strategy = strategy
def do_work(self, data):
return self._strategy.execute(data)

✅ Multiple algorithms for same task
✅ Need runtime algorithm switching
✅ Growing if/else chain for algorithm selection
✅ Want to test algorithms independently
✅ Algorithms should be interchangeable

❌ Only one algorithm
❌ Algorithm never changes
❌ Simple 2-3 branch conditionals
❌ Over-engineering simple problems

  • Strategy Pattern = Interchangeable algorithms
  • Strategy = Algorithm interface
  • Context = Uses strategy, allows switching
  • Benefit = Flexibility, testability, no conditionals
  • Principle = Open for extension, closed for modification
# 1. Strategy Interface
class Strategy(ABC):
@abstractmethod
def execute(self, data): pass
# 2. Concrete Strategies
class StrategyA(Strategy):
def execute(self, data):
return process_a(data)
class StrategyB(Strategy):
def execute(self, data):
return process_b(data)
# 3. Context
class Context:
def __init__(self, strategy: Strategy):
self._strategy = strategy
def set_strategy(self, strategy: Strategy):
self._strategy = strategy
def execute(self, data):
return self._strategy.execute(data)
# 4. Usage
context = Context(StrategyA())
context.execute(data)
context.set_strategy(StrategyB())
context.execute(data)
  • Strategy Pattern encapsulates algorithms into separate classes
  • It enables runtime switching of algorithms
  • It follows Open/Closed Principle - easy to add new strategies
  • Use it when you need multiple interchangeable algorithms
  • Don’t use it for simple cases where if/else is clearer!

What to say:

“Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one in a separate class, and makes them interchangeable. The pattern lets the algorithm vary independently from clients that use it, enabling runtime behavior changes without modifying the client code.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of encapsulation
  • Indicates you can explain concepts clearly

Must mention:

  • Multiple algorithms - Different ways to accomplish the same task
  • Runtime switching - Need to change behavior dynamically
  • Eliminate conditionals - Replace if/else chains
  • Testing isolation - Test each algorithm independently
  • Open/Closed Principle - Add algorithms without modifying existing code

Example scenario to give:

“I’d use Strategy Pattern when building a payment processing system. Each payment method - Credit Card, PayPal, Crypto - is a different strategy. The checkout process doesn’t care which payment method is used; it just calls the pay() method. Users can switch payment methods at runtime, and adding new payment methods is just creating a new strategy class.”

Must discuss:

  • Strategy - Client chooses the strategy explicitly, algorithms are interchangeable
  • State - Object changes behavior based on internal state automatically
  • Key difference - Who controls the switch (client vs object) and why

Example to give:

“Strategy Pattern is like choosing a shipping method at checkout - YOU choose between standard, express, or overnight. State Pattern is like a vending machine - it changes behavior automatically based on whether it has items, received payment, etc. With Strategy, the client decides. With State, the object decides based on its state.”

Must discuss:

Example to give:

“Strategy Pattern strongly supports the Open/Closed Principle - you can add new sorting algorithms without modifying the SortingApplication class. It also supports Single Responsibility because each strategy class has one job - implementing one specific algorithm. The context depends on the Strategy interface, not concrete implementations, supporting Dependency Inversion.”

Benefits to mention:

  • Runtime flexibility - Change algorithms dynamically
  • No conditionals - Eliminate if/else chains
  • Easy testing - Test each strategy independently
  • Code organization - Each algorithm in its own class
  • Reusability - Strategies can be reused across contexts

Trade-offs to acknowledge:

  • More classes - Each algorithm is a separate class
  • Client awareness - Client must know about different strategies
  • Complexity for simple cases - Overkill for 2-3 simple algorithms
  • Configuration overhead - Need to configure and inject strategies

Q: “How does Strategy Pattern eliminate conditionals?”

A:

“Instead of having a switch statement or if/else chain that checks which algorithm to use, we delegate to a strategy object. The context just calls strategy.execute() - it doesn’t know or care which concrete strategy is being used. Adding a new algorithm doesn’t require modifying any existing code, just creating a new strategy class.”

Q: “When would you NOT use Strategy Pattern?”

A:

“I wouldn’t use Strategy Pattern when there’s only one algorithm, when the algorithm never changes, or for simple 2-3 branch conditionals where the overhead isn’t justified. The pattern adds complexity with multiple classes, so for simple cases, a direct if/else might be clearer and more maintainable.”

Q: “How does Strategy Pattern relate to Dependency Injection?”

A:

“Strategy Pattern and Dependency Injection work together beautifully. The strategy is injected into the context, allowing the algorithm to be configured externally. This makes the context more flexible and testable - you can inject mock strategies in tests. In frameworks like Spring, strategies are often injected through constructor injection.”

Before your interview, make sure you can:

  • Define Strategy Pattern clearly in one sentence
  • Explain when to use it (with examples)
  • Describe the structure: Strategy, Concrete Strategy, Context
  • Implement Strategy Pattern from scratch
  • Compare with State Pattern
  • List benefits and trade-offs
  • Connect to SOLID principles
  • Identify when NOT to use it
  • Give 2-3 real-world examples (payment, sorting, compression)
  • Discuss functional vs class-based strategies

Remember: Strategy Pattern is about interchangeable algorithms - define a family of algorithms, encapsulate each one, and swap them at runtime! 🔄