Tired of giant switch
blocks? Learn how the Strategy Pattern helps you write cleaner, extensible, and testable code with real-world JavaScript examples.

switch
blocks? Learn how the Strategy Pattern helps you write cleaner, extensible, and testable code with real-world JavaScript examples.Introduction
We’ve all been there: you’re working on a feature, and suddenly your function grows into a 100-line switch
statement. Each new case makes the function harder to read, harder to test, and a nightmare to extend.
Sure, switch
works, but it locks you into rigid, unmaintainable code. Every time business rules change, you’re editing the same giant block. That’s a recipe for bugs.
This is exactly where the Strategy Pattern shines. Instead of cramming logic into a single branching statement, you define strategies (algorithms, behaviors, rules) separately and switch them dynamically at runtime.
In this article, you’ll learn:
- ✅ Why
switch
Statements become a problem in real projects - ✅ How the Strategy Pattern solves it elegantly
- ✅ Practical JavaScript/TypeScript examples
- ✅ Real-world cases: payments, sorting, feature toggles
- ✅ Gotchas to watch out for
By the end, you’ll see why the Strategy Pattern is a must-have tool for writing clean, extensible code.
The Problem With Switch Statements
Switch statements are fine for small, fixed sets of cases, but they scale poorly.
Example: Payment Processing
function processPayment(method: string, amount: number) {
switch (method) {
case "paypal":
console.log(`Paid ${amount} using PayPal`);
break;
case "stripe":
console.log(`Paid ${amount} using Stripe`);
break;
case "credit":
console.log(`Paid ${amount} using Credit Card`);
break;
default:
throw new Error("Unsupported payment method");
}
}
This works… until your PM asks you to:
- Add Apple Pay support
- Handle different discount rules per provider
- Log audit trails differently
Suddenly, the switch
balloons into an untestable monster. Every change means touching the same brittle block of code.
Enter the Strategy Pattern
The Strategy Pattern is a behavioral design pattern that lets you define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.
👉 Instead of switching on behavior, you delegate it to the right strategy.
Analogy
Think of a payment counter at a mall. The cashier doesn’t care whether you use PayPal, a credit card, or cash. They just call the right payment strategy and move on.
Implementing Strategy Pattern in JavaScript/TypeScript
Step 1: Define a Common Interface
interface PaymentStrategy {
pay(amount: number): void;
}
Step 2: Create Concrete Strategies
class PayPalPayment implements PaymentStrategy {
pay(amount: number) {
console.log(`Paid ${amount} using PayPal`);
}
}
class StripePayment implements PaymentStrategy {
pay(amount: number) {
console.log(`Paid ${amount} using Stripe`);
}
}
class CreditCardPayment implements PaymentStrategy {
pay(amount: number) {
console.log(`Paid ${amount} using Credit Card`);
}
}
Step 3: Context That Uses a Strategy
class PaymentContext {
constructor(private strategy: PaymentStrategy) {}
setStrategy(strategy: PaymentStrategy) {
this.strategy = strategy;
}
pay(amount: number) {
this.strategy.pay(amount);
}
}
Step 4: Use It in Real Code
const payment = new PaymentContext(new PayPalPayment());
payment.pay(100); // Paid 100 using PayPal
payment.setStrategy(new StripePayment());
payment.pay(250); // Paid 250 using Stripe
Now, adding Apple Pay means just creating a new strategy; no need to touch existing code.
Real-World Scenarios
1. Sorting Algorithms
Switch between quicksort, mergesort, or bubble sort at runtime based on dataset size.
2. Feature Toggles
Define different rollout strategies (percentage rollout, region-based, A/B testing).
3. Authentication
Swap between OAuth, JWT, or session-based authentication dynamically.
4. Game Development
Character movement strategies (walk, fly, swim) without giant conditionals.
Benefits of the Strategy Pattern
- No more bloated switch/if-else blocks
- Open/Closed Principle friendly: add new strategies without editing old code
- Unit testing made simple: test each strategy in isolation
- Runtime flexibility: change strategy dynamically
Gotchas to Watch Out For
- Overengineering risk: If you only have two small cases, a
switch
is fine. Don’t apply patterns blindly. - Too many small classes: Organize them logically (e.g., inside a
/strategies
folder). - Context switching costs: If strategies share state, be mindful of dependencies.
Pro Tip: Strategy with Maps
For simpler cases, you can implement a lightweight Strategy Pattern using a JavaScript object instead of classes:
const strategies: Record<string, (amount: number) => void> = {
paypal: (amt) => console.log(`Paid ${amt} using PayPal`),
stripe: (amt) => console.log(`Paid ${amt} using Stripe`),
credit: (amt) => console.log(`Paid ${amt} using Credit Card`),
};
function processPayment(method: string, amount: number) {
const strategy = strategies[method];
if (!strategy) throw new Error("Unsupported payment method");
strategy(amount);
}
processPayment("paypal", 100);
processPayment("stripe", 200);
This is Strategy in spirit, with less boilerplate.
Conclusion
The next time you’re staring at a growing switch
statement, ask yourself:
👉 “Am I defining different strategies here?”
If yes, pull that logic into a Strategy Pattern. You’ll end up with code that’s easier to maintain, test, and extend, especially in growing projects.
Call to Action
Have you refactored a messy switch
statement into a cleaner pattern before?
Drop your story in the comments 👇
And if your teammate keeps writing 300-line switch
blocks, share this post with them. You’ll both thank me later.
Leave a Reply