🔄 Behavioral

Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it, enabling runtime selection of the most appropriate algorithm without modifying the context class.

Problem

You have a class that does something important, but you want to offer multiple variants of the algorithm it uses. Adding each variant via conditionals or subclasses bloats the class, makes it harder to maintain, and violates the open/closed principle. Changing one algorithm risks breaking another.

Solution

Extract each algorithm into its own class, all implementing a common interface. The context class stores a reference to one of the strategy objects and delegates the work. The context doesn't know which concrete strategy it uses—it works with all strategies through the generic interface. Strategies can be swapped at runtime.

Participants

RoleResponsibility
StrategyDeclares an interface common to all supported algorithms
ConcreteStrategyImplements the algorithm using the Strategy interface
ContextMaintains a reference to a Strategy object and delegates algorithm work to it

TypeScript Example

TypeScript
interface CompressionStrategy {
  compress(data: string): string;
  name: string;
}

const gzip: CompressionStrategy = {
  name: "gzip",
  compress(data) {
    return `[gzip:${data.length}bytes]${data.slice(0, 10)}...`;
  },
};

const brotli: CompressionStrategy = {
  name: "brotli",
  compress(data) {
    return `[br:${Math.floor(data.length * 0.7)}bytes]${data.slice(0, 8)}...`;
  },
};

const none: CompressionStrategy = {
  name: "identity",
  compress(data) { return data; },
};

class HttpResponse {
  constructor(private strategy: CompressionStrategy) {}

  setCompression(strategy: CompressionStrategy) {
    this.strategy = strategy;
  }

  send(body: string): string {
    const compressed = this.strategy.compress(body);
    return `Content-Encoding: ${this.strategy.name}\n${compressed}`;
  }
}

const response = new HttpResponse(gzip);
console.log(response.send("Hello World! ".repeat(100)));

response.setCompression(brotli);
console.log(response.send("Hello World! ".repeat(100)));

Real-World Example

Sorting algorithms in array libraries allow choosing between quicksort, mergesort, and heapsort depending on the data characteristics. Payment processing systems use strategies for different providers (Stripe, PayPal, Square). Map applications switch routing strategies between shortest path, fastest route, and avoiding tolls. Compression algorithms in HTTP (gzip, brotli, deflate) are strategies chosen based on client support.

When to Use

  • You want to use different variants of an algorithm at runtime
  • You have many related classes that differ only in their behavior
  • You want to isolate the algorithm's implementation details from the code that uses it
  • A class has a massive conditional operator that switches between different variants of the same algorithm

When Not to Use

  • There are only a couple of algorithms and they rarely change
  • Clients don't need to know about different strategies (the algorithm is an implementation detail)
  • The overhead of extra classes and objects is not justified for a simple behavior change

Understanding the Strategy Pattern

The Strategy pattern is one of the most important behavioral design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.

Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it, enabling runtime selection of the most appropriate algorithm without modifying the context class. The pattern involves the following key participants: Strategy, ConcreteStrategy, Context. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.

In TypeScript and modern JavaScript development, the Strategy pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Strategy implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.

Knowing when to apply the Strategy pattern is as important as knowing how. Common use cases include: You want to use different variants of an algorithm at runtime; You have many related classes that differ only in their behavior; You want to isolate the algorithm's implementation details from the code that uses it. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.

The Strategy pattern is related to several other design patterns: Bridge, State, Template Method, Command, Decorator. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.

Related Patterns

More Behavioral Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.