Strategy Pattern β Interchangeable Algorithms (2026)
In this tutorial, you'll learn how the strategy pattern lets you swap algorithms at runtime, how it eliminates massive if-else chains, and how real payment systems use it to support multiple processors.
Think of a GPS navigation app. You need directions from A to B. The app offers multiple strategies: "shortest distance," "fastest route," "avoid tolls," "scenic route." Each strategy calculates the route differently, but they all return the same thing β directions. You can switch strategies at any time without changing the navigation system. The strategy pattern is exactly this: a family of algorithms, each encapsulated and interchangeable.
Core Concept
The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The algorithm can vary independently from the clients that use it. Instead of a single class with multiple conditional branches, each algorithm gets its own class implementing a common interface.
// The strategy interface
interface PaymentStrategy {
pay(amount: number): Promise<PaymentResult>;
}
// Concrete strategies
class CreditCardStrategy implements PaymentStrategy {
constructor(private cardNumber: string, private cvv: string) {}
async pay(amount: number): Promise<PaymentResult> {
// Process credit card payment
const response = await creditCardApi.charge(this.cardNumber, amount);
return { success: true, transactionId: response.id };
}
}
class PayPalStrategy implements PaymentStrategy {
constructor(private email: string) {}
async pay(amount: number): Promise<PaymentResult> {
const response = await paypalApi.transfer(this.email, amount);
return { success: true, transactionId: response.id };
}
}
class CryptoStrategy implements PaymentStrategy {
constructor(private walletAddress: string) {}
async pay(amount: number): Promise<PaymentResult> {
const tx = await cryptoApi.send(this.walletAddress, amount);
return { success: true, transactionId: tx.hash };
}
}
// The context class uses any strategy
class CheckoutService {
constructor(private strategy: PaymentStrategy) {}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
async checkout(amount: number): Promise<PaymentResult> {
// Before payment logic
this.logger.info(`Processing payment of $${amount}`);
// Delegate to the strategy
const result = await this.strategy.pay(amount);
// After payment logic
this.logger.info(`Payment ${result.success ? "succeeded" : "failed"}`);
return result;
}
}
How It Works
The pattern has three participants:
- Context β the class that uses a strategy (e.g.,
CheckoutService) - Strategy interface β the contract all strategies implement
- Concrete strategies β the actual algorithms
ββββββββββββββββββββ βββββββββββββββββββββββ
β Context β uses β β Β«Strategy InterfaceΒ» β
β (CheckoutService)β β + pay(amount) β
ββββββββββββββββββββ βββββββββββββββββββββββ
β
βββββββββββΌββββββββββ
β β β
ββββββββββββββββ ββββββββββββ ββββββββββββ
β CreditCard β β PayPal β β Crypto β
ββββββββββββββββ ββββββββββββ ββββββββββββ
The client creates a strategy and passes it to the context. The context calls the strategy's method without knowing which concrete implementation is running. Strategies can be swapped at runtime via a setter.
Eliminating Switch Statements
// β Without Strategy β growing if-else chain
function processPayment(type: string, amount: number) {
if (type === "credit_card") {
// 50 lines of credit card logic
} else if (type === "paypal") {
// 50 lines of PayPal logic
} else if (type === "crypto") {
// 50 lines of crypto logic
}
// Adding a new type = modifying this function
}
// β
With Strategy β each algorithm is isolated
const strategies: Record<string, PaymentStrategy> = {
credit_card: new CreditCardStrategy(cardNum, cvv),
paypal: new PayPalStrategy(email),
crypto: new CryptoStrategy(wallet),
};
const strategy = strategies[type];
const result = await strategy.pay(amount);
// Adding a new type = adding a new class & registering it
Expected output: The strategy version is open for extension (add a new class) but closed for modification (no changes to existing code). This follows the Open/Closed Principle.
Real-World Examples
File Compression
DodaZIP uses the strategy pattern for compression algorithms. The context is the Compressor class, and strategies include ZipStrategy, GzipStrategy, TarStrategy, and SevenZipStrategy. Users select the format at runtime, and the compressor delegates to the appropriate algorithm.
Validation Rules
Form validation often uses the strategy pattern. Each validation rule (required, email format, min length, unique check) is a strategy. The form validator receives a list of strategies and runs them in sequence. Adding a new validation doesn't require changing the validator class.
interface ValidationStrategy {
validate(value: unknown): string | null; // null = valid, string = error
}
class EmailValidator implements ValidationStrategy {
validate(value: unknown): string | null {
const email = value as string;
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
? null : "Invalid email format";
}
}
class FormValidator {
constructor(private strategies: ValidationStrategy[]) {}
validate(data: Record<string, unknown>): Record<string, string> {
const errors: Record<string, string> = {};
for (const [field, strategies] of Object.entries(this.strategies)) {
// Each field has its own set of strategies
}
return errors;
}
}
Pros & Cons
| Pros | Cons |
|---|---|
| Eliminates massive conditional statements | Increases number of classes |
| Follows Open/Closed Principle | Clients must know about available strategies |
| Algorithms can be reused across contexts | Over-engineering if only 1-2 algorithms exist |
| Strategies can be swapped at runtime | Strategy selection logic can grow complex |
When to Use
Use the strategy pattern when:
- You have multiple related algorithms that differ only in behavior
- You need to swap algorithms at runtime
- You want to avoid conditional statements for algorithm selection
- The algorithm tends to change or grow β isolating it prevents cascading changes
Avoid it when you have only one algorithm that's unlikely to change, or when the conditional logic is simple and unlikely to grow.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro