← Back to Home

Strategy Pattern

Define a family of algorithms, encapsulate each one, and make them interchangeable without breaking existing code.

What is it?

The Strategy pattern allows you to select algorithms at runtime without modifying client code or creating complex conditional logic that breaks when extended.

Interactive Demo: Strategy vs Hardcoded Logic

Test a discount system. Watch what happens when a NEW discount type (Flash Sale) is needed:

Shopping Scenario:

📦 Purchase Amount: $100
🎓 VIP Membership: 3 years
Flash Sale Time Left: 2 hours

⚡ Strategy Pattern

Activity Log:
Try the discount buttons above...

🔀 Hardcoded Logic

Activity Log:
Try the buttons above...

💡 Key Insight:

  • Strategy Pattern: NEW Flash Sale works immediately - no code changes needed!
  • Hardcoded Logic: NEW Flash Sale CRASHES - missing switch case breaks production!
  • Real Impact: Strategy enables safe extension, hardcoded logic creates maintenance nightmares

Code Example

interface DiscountStrategy {
  calculate(amount: number, details?: any): number;
  getName(): string;
}

class FlashSaleDiscount implements DiscountStrategy {
  calculate(amount: number, details: { hoursLeft: number }): number {
    if (details.hoursLeft <= 1) return amount * 0.50; // 50% off
    if (details.hoursLeft <= 6) return amount * 0.35; // 35% off
    return amount * 0.25; // 25% off
  }
  
  getName(): string { return "Flash Sale"; }
}

class DiscountCalculator {
  private strategy: DiscountStrategy;

  setStrategy(strategy: DiscountStrategy) {
    this.strategy = strategy; // Clean algorithm switching
  }

  calculate(amount: number, details?: any): number {
    return this.strategy.calculate(amount, details); // No conditionals!
  }
}

// Usage - New strategies work immediately
const calculator = new DiscountCalculator();
calculator.setStrategy(new FlashSaleDiscount());
const discount = calculator.calculate(100, { hoursLeft: 2 });

// vs Hardcoded Approach - Breaks when extended
class HardcodedCalculator {
  static calculate(amount: number, type: string, details?: any): number {
    switch (type) { // This switch must be updated everywhere!
      case "student": return amount * 0.15;
      case "senior": return amount * 0.20;
      case "vip": return amount * 0.25;
      // Missing "flash" case = Runtime Error!
      default: throw new Error(`Unknown type: ${type}`);
    }
  }
}

Common Uses

  • Payment processing (credit card, PayPal, crypto, buy-now-pay-later)
  • Pricing strategies (regular, discount, subscription, bulk pricing)
  • Data validation (email, phone, password strength, custom rules)
  • File export formats (PDF, Excel, CSV, JSON)
  • Authentication methods (OAuth, JWT, API keys, biometric)
  • Sorting/filtering algorithms (performance vs memory trade-offs)

When to Use

  • When you need multiple ways to perform the same task
  • When algorithms should be interchangeable at runtime
  • To eliminate complex if/else or switch statement chains
  • When new algorithm variants are frequently added
  • When algorithms have different performance characteristics

Caution

  • Increases the number of classes (but improves maintainability)
  • Clients must understand available strategies
  • Overkill for simple algorithms that never change
  • Strategy selection logic still needs to exist somewhere