JavaScript Strategy Pattern: Complete Guide

The strategy pattern lets you swap algorithms at runtime by encapsulating each one in a separate object or function. Learn how to use it to write flexible, maintainable JavaScript code.

7 min read

The strategy pattern lets you change how a piece of code behaves by swapping out the algorithm it uses, at runtime, without changing the code that uses it. You define a family of interchangeable algorithms, encapsulate each one, and let the caller choose which one to plug in.

Think of a navigation app. You enter a destination. The app can calculate a route by "fastest time," "shortest distance," or "avoid highways." The core navigation logic stays the same. Only the routing algorithm changes. Each routing option is a strategy.

The Problem: Growing Conditional Chains

Here is code that calculates shipping costs without the strategy pattern:

javascriptjavascript
function calculateShipping(method, weight) {
  if (method === "standard") {
    return weight * 1.5 + 5;
  } else if (method === "express") {
    return weight * 3.0 + 10;
  } else if (method === "overnight") {
    return weight * 5.0 + 20;
  } else if (method === "international") {
    return weight * 8.0 + 35;
  }
  throw new Error(`Unknown shipping method: ${method}`);
}
 
console.log(calculateShipping("express", 2)); // 16

Every new shipping method means adding another else if branch. The function grows. Testing gets harder. The code that uses calculateShipping must know about all the method strings.

The Solution: Encapsulate Each Strategy

Turn each shipping method into its own function:

javascriptjavascript
const shippingStrategies = {
  standard(weight) {
    return weight * 1.5 + 5;
  },
  express(weight) {
    return weight * 3.0 + 10;
  },
  overnight(weight) {
    return weight * 5.0 + 20;
  },
  international(weight) {
    return weight * 8.0 + 35;
  }
};
 
function calculateShipping(method, weight) {
  const strategy = shippingStrategies[method];
  if (!strategy) {
    throw new Error(`Unknown shipping method: ${method}`);
  }
  return strategy(weight);
}
 
console.log(calculateShipping("express", 2)); // 16

The context function calculateShipping now delegates to a strategy object. Adding a new shipping method is one line in the shippingStrategies object. The context function never changes.

Strategy pattern: context delegates to interchangeable strategies

The context only knows that it has a method name and a weight. It looks up the matching strategy and delegates. Each strategy is a self-contained function that can be tested in isolation.

Passing Strategies as Arguments

Strategies do not need to live in a lookup object. You can pass them directly:

javascriptjavascript
function processPayment(amount, paymentStrategy) {
  console.log(`Processing $${amount}...`);
  const result = paymentStrategy(amount);
  console.log(result);
}
 
const creditCard = (amount) => `Charged $${amount} to credit card`;
const paypal = (amount) => `Sent $${amount} via PayPal`;
const crypto = (amount) => `Transferred $${amount} in USDC`;
 
processPayment(99, creditCard);
// Processing $99...
// Charged $99 to credit card
 
processPayment(50, paypal);
// Processing $50...
// Sent $50 via PayPal

This is the most common form of the strategy pattern in JavaScript. Higher-order functions, callback functions, and event handlers are all examples of passing a strategy as an argument.

Strategy as a Class-Like Object

When a strategy needs its own state or configuration, use an object with a standard method name:

javascriptjavascript
class DiscountStrategy {
  calculate(price) {
    return price; // No discount by default
  }
}
 
class PercentageDiscount extends DiscountStrategy {
  constructor(percent) {
    super();
    this.percent = percent;
  }
 
  calculate(price) {
    return price * (1 - this.percent / 100);
  }
}
 
class FixedDiscount extends DiscountStrategy {
  constructor(amount) {
    super();
    this.amount = amount;
  }
 
  calculate(price) {
    return Math.max(0, price - this.amount);
  }
}
 
function getFinalPrice(price, discountStrategy) {
  return discountStrategy.calculate(price);
}
 
const tenPercent = new PercentageDiscount(10);
const fiveDollarsOff = new FixedDiscount(5);
 
console.log(getFinalPrice(100, tenPercent));     // 90
console.log(getFinalPrice(100, fiveDollarsOff)); // 95

Every strategy has a calculate method. The context getFinalPrice only calls discountStrategy.calculate(price). It does not know or care whether the discount is percentage-based or fixed.

For more on using classes this way, see the JavaScript classes guide.

Real-World Use Case: Formatter Strategies

A practical example: a data display component that formats values differently depending on context:

javascriptjavascript
const formatters = {
  currency(value) {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD"
    }).format(value);
  },
  percent(value) {
    return `${(value * 100).toFixed(1)}%`;
  },
  compact(value) {
    if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
    if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;
    return String(value);
  },
  default(value) {
    return String(value);
  }
};
 
function renderCell(value, format) {
  const formatter = formatters[format] || formatters.default;
  return formatter(value);
}
 
console.log(renderCell(1234.56, "currency")); // $1,234.56
console.log(renderCell(0.873, "percent"));     // 87.3%
console.log(renderCell(45000, "compact"));     // 45.0K

No if or switch in renderCell. Every format is its own function. Adding a new format is one entry in the formatters object.

Strategy Pattern vs If-Else: When to Switch

Use if-else/switch whenUse strategy pattern when
Two or three branchesFour or more branches
Branches will not growBranches are likely to grow
Logic is simple one-linersEach branch has nontrivial logic
All branches in one fileBranches may come from different modules

The inflection point is typically three branches. Below that, conditionals are clearer. Above that, the strategy pattern pays for its setup cost.

Common Mistake: Over-Engineering

Not every conditional needs to become a strategy:

javascriptjavascript
// Over-engineered for a simple case
const greetingStrategies = {
  morning: () => "Good morning",
  afternoon: () => "Good afternoon",
  evening: () => "Good evening"
};
 
// A simple object lookup is clearer here
const greetings = {
  morning: "Good morning",
  afternoon: "Good afternoon",
  evening: "Good evening"
};

The strategy pattern is about encapsulating behavior (functions), not data (strings). If your "strategies" return static values, use a plain object lookup instead.

Rune AI

Rune AI

Key Insights

  • The strategy pattern encapsulates interchangeable algorithms behind a common interface.
  • It replaces long if-else or switch chains with composable behavior objects.
  • In JavaScript, strategies are often just functions passed as arguments.
  • The context delegates work to the strategy without knowing its internals.
  • Adding a new strategy means writing one new function, not modifying existing code.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between the strategy pattern and the factory pattern?

The factory pattern is about creating objects. The strategy pattern is about choosing behaviors. A factory decides which object to create. A strategy pattern lets you pass in which behavior to use.

Can I use plain functions instead of classes for strategies?

Absolutely. In JavaScript, a strategy can be a function, an object with a method, or a class instance. Functions are the simplest and most common choice.

When should I avoid the strategy pattern?

Avoid it when you only have two behaviors that will never change. A simple if-else is clearer. The strategy pattern earns its value when you have three or more behaviors, or when behaviors are likely to grow over time.

Conclusion

The strategy pattern is about choosing behaviors at runtime instead of hard-coding them with conditionals. Encapsulate each algorithm in a function or object, pass the one you want to the context, and let the context delegate to it. The result is code that is easier to extend and test.The strategy pattern replaces conditional chains with interchangeable behavior objects. Define each algorithm as a separate function. Let the context accept a strategy and delegate to it. Add new behaviors by writing new functions, not by editing existing if-else chains. In JavaScript, the pattern is often so natural you do not even call it by name. Passing a callback to Array.sort(), a comparison function to Array.filter(), or a reducer to Array.reduce() are all strategy pattern applications. Recognizing the pattern helps you use it intentionally when your own conditional chains start to grow.