Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

Strategy Pattern 🎯

Overview

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

Difficulty: ⭐ Easy
Category: Behavioral Pattern
Interview Frequency: ⭐⭐⭐⭐ Very Common

Problem It Solves

Imagine you have different ways to perform the same task (e.g., different payment methods, sorting algorithms, compression algorithms). Without the Strategy pattern, you might:

  1. Use massive if-else or switch statements
  2. Create tightly coupled code
  3. Violate Open/Closed Principle (need to modify code to add new strategies)

Real-World Analogy

Think of traveling from point A to B:

  • By Car: Fast but expensive
  • By Bus: Slower but cheaper
  • By Walking: Free but slowest

All strategies achieve the same goal (travel), but the implementation differs. You choose based on your needs.

When to Use

Use Strategy Pattern when:

  • You have multiple ways to perform a task
  • You want to avoid conditionals for selecting behavior
  • You need runtime algorithm switching
  • Algorithms have different data structures

Don't use when:

  • You only have 2-3 simple variations (overkill)
  • Strategies don't change at runtime
  • The algorithm is simple and unlikely to change

Implementation

See strategy.ts for the complete implementation.

Key Components

  1. Strategy Interface: Defines the common interface for all algorithms
  2. Concrete Strategies: Implement the strategy interface with different algorithms
  3. Context: Maintains a reference to a strategy and delegates work to it

TypeScript Example

// Real-world example: Payment Processing System

interface PaymentStrategy {
  pay(amount: number): void;
}

class CreditCardPayment implements PaymentStrategy {
  pay(amount: number): void {
    console.log(`Paid ${amount} using Credit Card`);
  }
}

class PayPalPayment implements PaymentStrategy {
  pay(amount: number): void {
    console.log(`Paid ${amount} using PayPal`);
  }
}

class ShoppingCart {
  private strategy: PaymentStrategy;

  setPaymentStrategy(strategy: PaymentStrategy): void {
    this.strategy = strategy;
  }

  checkout(amount: number): void {
    this.strategy.pay(amount);
  }
}

Benefits

✅ Open/Closed Principle - Add new strategies without modifying existing code
✅ Eliminates conditional statements
✅ Runtime flexibility
✅ Better testability
✅ Clear separation of concerns

Drawbacks

❌ Increased number of classes
❌ Clients must be aware of different strategies
❌ Slight performance overhead (usually negligible)

Interview Questions & Answers

Q1: What's the difference between Strategy and State pattern?

Answer: Both look similar structurally, but:

  • Strategy: Algorithms are independent; client chooses which to use
  • State: States are interdependent; context changes state based on internal logic
  • Strategy: Focuses on HOW to do something
  • State: Focuses on WHAT state we're in

Q2: Can you use functional programming instead of Strategy pattern?

Answer: Yes! In languages with first-class functions (like TypeScript/JavaScript), you can pass functions instead of strategy objects:

type PaymentFunction = (amount: number) => void;

class ShoppingCart {
  checkout(amount: number, paymentFn: PaymentFunction): void {
    paymentFn(amount);
  }
}

// Usage
cart.checkout(100, (amount) => console.log(`Paid ${amount} with Credit Card`));

This is often simpler for simple cases, but Strategy pattern is better when:

  • Strategies have state
  • Strategies have multiple methods
  • You need dependency injection

Q3: How do you add a new payment method without modifying existing code?

Answer: Simply create a new class implementing PaymentStrategy:

class CryptoPayment implements PaymentStrategy {
  pay(amount: number): void {
    console.log(`Paid ${amount} using Cryptocurrency`);
  }
}

No changes needed to ShoppingCart or other payment methods!

Q4: When would you NOT use Strategy pattern?

Answer:

  • Only 2 simple alternatives → Use ternary or simple if-else
  • Algorithm never changes → Hard-code it
  • Performance is critical → Strategy adds indirection overhead
  • Strategies are just data, not behavior → Use configuration

Q5: How do you handle strategies that need different parameters?

Answer: Several approaches:

  1. Common interface with optional parameters
  2. Strategy-specific setup methods
  3. Constructor injection
  4. Context provides all needed data

Example:

interface PaymentStrategy {
  pay(amount: number, details?: any): void;
}

class CreditCardPayment implements PaymentStrategy {
  constructor(private cardNumber: string, private cvv: string) {}

  pay(amount: number): void {
    // Use this.cardNumber and this.cvv
  }
}

Real-World Use Cases

  1. E-commerce: Payment methods (Credit Card, PayPal, Crypto)
  2. Sorting: Different algorithms based on data size (QuickSort, MergeSort, BubbleSort)
  3. Compression: ZIP, RAR, GZIP
  4. Routing: Car, Bicycle, Walking (Google Maps)
  5. Authentication: OAuth, JWT, Basic Auth
  6. Logging: Console, File, Remote server
  7. Validation: Email, Phone, Password rules

Code Smells Strategy Pattern Fixes

Before (Code Smell):

class PaymentProcessor {
  processPayment(amount: number, method: string) {
    if (method === "creditcard") {
      // Credit card logic
    } else if (method === "paypal") {
      // PayPal logic
    } else if (method === "crypto") {
      // Crypto logic
    }
    // Adding new method requires modifying this function!
  }
}

After (Strategy Pattern):

class PaymentProcessor {
  process(amount: number, strategy: PaymentStrategy) {
    strategy.pay(amount);
  }
}
// Adding new methods doesn't require changing existing code!

Testing Tips

Strategy pattern makes testing easier:

  • Test each strategy in isolation
  • Mock strategies when testing context
  • Test strategy switching logic separately

Related Patterns

  • State Pattern: Similar structure, different intent
  • Template Method: Defines algorithm skeleton in base class
  • Command Pattern: Encapsulates requests, not algorithms
  • Factory Pattern: Often used to create strategies

Summary

The Strategy Pattern is essential for writing maintainable, extensible code. It's one of the most practical patterns and appears frequently in interviews. Master it by:

  1. Understanding when conditional logic should become strategies
  2. Practicing with real-world examples
  3. Explaining trade-offs clearly
  4. Being able to implement it quickly

Next: Try the Factory Pattern