Strategy Pattern
Behavioral design pattern that defines a family of interchangeable algorithms and makes them independent from clients using them.
Updated on January 11, 2026
The Strategy pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one individually, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it, promoting composition over inheritance. It provides an elegant alternative to complex conditional structures and facilitates adding new behaviors without modifying existing code.
Fundamentals of the Strategy Pattern
- Encapsulation of algorithms in separate classes implementing a common interface
- Decoupling between the usage context and the concrete algorithm implementation
- Dynamic selection of the algorithm to execute based on business context
- Compliance with the Open/Closed Principle: open for extension, closed for modification
Benefits of the Strategy Pattern
- Eliminates complex conditional structures (if/switch) in favor of polymorphism
- Facilitates unit testing by allowing isolation and mocking of each strategy
- Improves maintainability by isolating each algorithm in its own class
- Enables runtime behavior changes without impacting client code
- Respects the Single Responsibility Principle by separating algorithmic concerns
Practical Example: Payment System
// Common interface for all strategies
interface PaymentStrategy {
pay(amount: number): Promise<PaymentResult>;
validate(): boolean;
}
// Concrete strategy: Credit Card
class CreditCardStrategy implements PaymentStrategy {
constructor(
private cardNumber: string,
private cvv: string,
private expiryDate: string
) {}
validate(): boolean {
return this.cardNumber.length === 16 && this.cvv.length === 3;
}
async pay(amount: number): Promise<PaymentResult> {
console.log(`Processing ${amount} via Credit Card`);
// Credit card payment logic
return { success: true, transactionId: 'CC-' + Date.now() };
}
}
// Concrete strategy: PayPal
class PayPalStrategy implements PaymentStrategy {
constructor(private email: string) {}
validate(): boolean {
return this.email.includes('@');
}
async pay(amount: number): Promise<PaymentResult> {
console.log(`Processing ${amount} via PayPal`);
// PayPal payment logic
return { success: true, transactionId: 'PP-' + Date.now() };
}
}
// Concrete strategy: Crypto
class CryptoStrategy implements PaymentStrategy {
constructor(private walletAddress: string) {}
validate(): boolean {
return this.walletAddress.startsWith('0x');
}
async pay(amount: number): Promise<PaymentResult> {
console.log(`Processing ${amount} via Cryptocurrency`);
// Crypto payment logic
return { success: true, transactionId: 'CRYPTO-' + Date.now() };
}
}
// Context that uses the strategy
class PaymentProcessor {
private strategy: PaymentStrategy;
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
async processPayment(amount: number): Promise<PaymentResult> {
if (!this.strategy.validate()) {
throw new Error('Invalid payment method configuration');
}
return await this.strategy.pay(amount);
}
}
// Usage
const processor = new PaymentProcessor();
// Client A: card payment
processor.setStrategy(new CreditCardStrategy('1234567890123456', '123', '12/25'));
await processor.processPayment(99.99);
// Client B: PayPal payment
processor.setStrategy(new PayPalStrategy('user@example.com'));
await processor.processPayment(49.50);
// Client C: crypto payment
processor.setStrategy(new CryptoStrategy('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'));
await processor.processPayment(199.00);Implementing the Strategy Pattern
- Identify algorithms or behaviors that vary and are likely to change
- Define a common interface (Strategy) with methods all strategies must implement
- Create concrete classes for each algorithm variant implementing the Strategy interface
- Develop a Context class that maintains a reference to the Strategy object and delegates execution
- Allow clients to choose and inject the appropriate strategy into the context
- Test each strategy in isolation to ensure proper functionality
Pro Tip
Combine the Strategy pattern with dependency injection to maximize flexibility. Use a factory or service locator to select the appropriate strategy based on business criteria.
Related Tools and Frameworks
- TypeScript - Strong typing of strategy interfaces for better safety
- NestJS - Providers and dependency injection for managing strategies
- Redux - Reducers as state transformation strategies
- Passport.js - Authentication based on interchangeable strategies
- Joi/Yup - Validation with configurable schema strategies
- AWS SDK - Customizable retry and error handling strategies
The Strategy pattern represents a strategic investment in code maintainability and scalability. By isolating business algorithms into interchangeable components, teams can iterate quickly on features.
