Strategy Pattern

Med

The Strategy Pattern defines a family of interchangeable algorithms, encapsulates each one, and makes them swappable at runtime. In JavaScript, first-class functions make this pattern natural — a strategy is simply a function passed as an argument.

Interactive Visualization

Creational Patterns

IIFE creates private scope, returns public API via closure
returnscloses overcallsIIFE Scopeprivate varsPublic APIConsumer
1 / 3

Understanding Strategy Pattern

The Strategy Pattern is one of the most natural patterns in JavaScript because functions are first-class citizens. Every time you pass a callback to Array.sort, Array.filter, or a middleware function, you are using the Strategy Pattern. The pattern formalizes this by defining a family of interchangeable algorithms and letting the caller choose which one to use at runtime.

The classic implementation involves a Context (the object that uses the strategy), a Strategy interface (the expected function signature), and Concrete Strategies (the specific implementations). In JavaScript, the "interface" is informal — strategies just need to accept the same parameters and return compatible values. A common approach is to store strategies in an object map, eliminating switch statements and making the system extensible.

The pattern shines when behavior varies based on configuration, user type, environment, or runtime conditions. Instead of littering code with conditionals, you select a strategy once and the context delegates all behavior to it. This makes the code more maintainable, testable (each strategy can be unit tested independently), and extensible (new strategies are added without modifying existing code).

Key Points

  • Encapsulates a family of algorithms behind a common interface
  • The context object delegates behavior to a strategy instead of implementing it directly
  • Strategies can be swapped at runtime without changing the context
  • In JavaScript, functions are first-class — a strategy is often just a callback parameter
  • Eliminates large if/else or switch chains by mapping types to strategy functions
  • Commonly used for validation, sorting, formatting, pricing, and authentication

Code Examples

Validation Strategies

const validators = {
  email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  phone: (value) => /^\d{10,}$/.test(value),
  required: (value) => value !== null && value !== '' && value !== undefined,
  minLength: (min) => (value) => value.length >= min,
};

function validate(value, strategies) {
  return strategies.every(strategy => {
    if (typeof strategy === 'function') return strategy(value);
    return validators[strategy]?.(value) ?? true;
  });
}

validate('alice@test.com', ['required', 'email']);       // true
validate('short', ['required', validators.minLength(10)]); // false

Each validation rule is a strategy. The validate function does not know or care how each rule works.

Pricing Strategy

const pricingStrategies = {
  regular: (price) => price,
  premium: (price) => price * 0.9,    // 10% discount
  vip: (price) => price * 0.75,       // 25% discount
  coupon: (discount) => (price) => price * (1 - discount),
};

class ShoppingCart {
  #items = [];
  #strategy = pricingStrategies.regular;

  setStrategy(strategy) { this.#strategy = strategy; }

  addItem(name, price) { this.#items.push({ name, price }); }

  getTotal() {
    const subtotal = this.#items.reduce((sum, i) => sum + i.price, 0);
    return this.#strategy(subtotal);
  }
}

const cart = new ShoppingCart();
cart.addItem('Laptop', 1000);
cart.setStrategy(pricingStrategies.vip);
console.log(cart.getTotal()); // 750

The cart delegates pricing to an external strategy. Changing strategy changes behavior without modifying the cart.

Sorting Strategies

const sortStrategies = {
  byName: (a, b) => a.name.localeCompare(b.name),
  byPrice: (a, b) => a.price - b.price,
  byPriceDesc: (a, b) => b.price - a.price,
  byRating: (a, b) => b.rating - a.rating,
};

function sortProducts(products, strategy) {
  return [...products].sort(strategy);
}

const products = [
  { name: 'Widget', price: 25, rating: 4.5 },
  { name: 'Gadget', price: 15, rating: 4.8 },
  { name: 'Doohickey', price: 35, rating: 4.2 },
];

sortProducts(products, sortStrategies.byPrice);
// [Gadget, Widget, Doohickey]

Array.sort already accepts a strategy (comparator). This pattern formalizes naming and reuse.

Common Mistakes

  • Overengineering — if you only ever have one algorithm, a strategy is overkill
  • Not defining a consistent interface for strategies, making them non-interchangeable
  • Putting strategy selection logic inside the context instead of the caller
  • Forgetting that in JavaScript, first-class functions already provide strategy-like behavior naturally

Interview Tips

  • Explain that Array.sort, Array.filter, and Promise.then all accept strategies (callbacks)
  • Show how the pattern replaces if/else chains with a strategy map
  • Discuss how strategies compose — a strategy can wrap another strategy (decorator + strategy)
  • Mention real-world uses: payment processors, auth providers, log formatters

Related Concepts