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
| Role | Responsibility |
|---|---|
| Strategy | Declares an interface common to all supported algorithms |
| ConcreteStrategy | Implements the algorithm using the Strategy interface |
| Context | Maintains a reference to a Strategy object and delegates algorithm work to it |
TypeScript Example
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
Decouples an abstraction from its implementation so that the two can vary independently. The Bridge pattern achieves thi…
Allows an object to alter its behavior when its internal state changes. The object appears to change its class. State en…
Defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses re…
Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requ…
Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing …
More Behavioral Patterns
Explore All Design Patterns
Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.