A while ago, I opened a file and immediately knew something was off.
Not because it was broken.
Not because it was complex.
But because it had that shape.
You know the one:
if (...) {
// something
} else if (...) {
// something else
} else if (...) {
// another thing
} else {
// fallback
}
It worked.
But every time we needed to add a new case, we had to go back into that block and make it just a bit worse.
And that’s when I realized:
This isn’t just messy code. This is a scaling problem.
Let’s fix it.
TL;DR
- Long
if-else chains are a signal that your logic doesn’t scale well.
- The Strategy Pattern helps you encapsulate behaviors and swap them easily.
- In JavaScript, you don’t need heavy OOP: functions are often enough.
Table of Contents
The Problem: The If-Else Monster
At first, if-else feels fine.
It’s simple.
It’s readable.
It gets the job done.
Until it doesn’t.
The moment your logic starts growing based on types, modes, or variants, things start getting messy.
And more importantly:
every new feature requires modifying existing logic
That’s the real problem.
A Real Example: Payment Methods
Let’s say you’re building a checkout system.
You need to handle multiple payment methods:
- credit card
- PayPal
- bank transfer
A typical implementation might look like this:
function processPayment(method, amount) {
if (method === "credit-card") {
console.log("Processing credit card payment:", amount)
// card logic
} else if (method === "paypal") {
console.log("Processing PayPal payment:", amount)
// paypal logic
} else if (method === "bank-transfer") {
console.log("Processing bank transfer:", amount)
// bank logic
} else {
throw new Error("Unsupported payment method")
}
}
It works, but you can already feel the tension.
Why This Doesn’t Scale
The issue isn’t the code itself.
It’s how it evolves.
Every time you add a new payment method:
- you modify this function
- you increase its complexity
- you risk breaking existing logic
After a few iterations, you end up with:
- long functions
- duplicated patterns
- fragile logic
This is exactly where the Strategy Pattern shines.
Introducing the Strategy Pattern
The idea is simple:
Instead of having one function decide how to do everything,
delegate each behavior to a separate “strategy”.
Each strategy handles one case.
The main function just selects which one to use.
In practice:
replace conditionals with a lookup-based approach
Step-by-Step Refactoring
Let’s improve the previous example step by step.
function payWithCreditCard(amount) {
console.log("Processing credit card payment:", amount)
}
function payWithPaypal(amount) {
console.log("Processing PayPal payment:", amount)
}
function payWithBankTransfer(amount) {
console.log("Processing bank transfer:", amount)
}
Each function now has a clear responsibility.
Step 2: Create a strategy map
const paymentStrategies = {
"credit-card": payWithCreditCard,
"paypal": payWithPaypal,
"bank-transfer": payWithBankTransfer,
}
This replaces the if-else chain with a simple lookup.
Step 3: Simplify the main function
function processPayment(method, amount) {
const strategy = paymentStrategies[method]
if (!strategy) {
throw new Error("Unsupported payment method")
}
strategy(amount)
}
No branching logic.
Just delegation.
Final Result: Clean and Extensible
Now adding a new payment method is straightforward:
function payWithCrypto(amount) {
console.log("Processing crypto payment:", amount)
}
paymentStrategies["crypto"] = payWithCrypto
No need to modify existing logic.
No risk of breaking unrelated behavior.
This is the real benefit: open for extension, closed for modification
Using TypeScript for Safer Strategies
So far, everything works perfectly in JavaScript.
And thanks to this guard:
if (!strategy) {
throw new Error("Unsupported payment method")
}
invalid inputs are handled safely.
However, the error is still detected at runtime.
With TypeScript, we can catch the same issue earlier, before the code even runs.
Step 1: Define allowed methods
type PaymentMethod = "credit-card" | "paypal" | "bank-transfer"
Now only valid methods are allowed.
Step 2: Type the strategies
type PaymentStrategy = (amount: number) => void
const paymentStrategies: Record<PaymentMethod, PaymentStrategy> = {
"credit-card": payWithCreditCard,
"paypal": payWithPaypal,
"bank-transfer": payWithBankTransfer,
}
Now TypeScript guarantees:
- every method has a strategy
- every strategy has the correct signature
Step 3: Type the main function
function processPayment(method: PaymentMethod, amount: number) {
const strategy = paymentStrategies[method]
strategy(amount)
}
No need for runtime checks anymore.
Errors are caught at compile time.
Why this matters
With JavaScript, you make your code flexible.
With TypeScript, you make it reliable.
You’re not just cleaning your code.
You’re making invalid states impossible.
Optional: Class-Based Strategy
If you prefer a more OOP-style approach with JavaScript, you can model strategies as classes:
class CreditCardStrategy {
execute(amount) {
console.log("Processing credit card payment:", amount)
}
}
class PayPalStrategy {
execute(amount) {
console.log("Processing PayPal payment:", amount)
}
}
Then use them like this:
const strategies = {
"credit-card": new CreditCardStrategy(),
"paypal": new PayPalStrategy(),
}
function processPayment(method, amount) {
const strategy = strategies[method]
if (!strategy) {
throw new Error("Unsupported payment method")
}
strategy.execute(amount)
}
It’s more structured, but also more verbose.
In JavaScript, the functional approach is usually enough.
When to Use (and Not Use) Strategy
Use it when:
- you have multiple variants of the same behavior
- new cases are added frequently
- conditionals are growing
Avoid it when:
- you only have a couple of simple cases
- the logic is unlikely to change
- it adds unnecessary complexity
The goal is not to apply patterns, but to solve real problems.
Final Thoughts
The Strategy Pattern isn’t about being clever, it’s about writing code that can grow without becoming fragile.
Most messy if-else chains don’t start messy, they become messy over time and by the time you notice, changing them feels risky.
The good news is:
You don’t need a rewrite, just a better way to organize behavior.
If this helped you:
- Leave a ❤️ reaction
- Share the worst
if-else monster you’ve seen
And if you enjoy this kind of content, follow me here on CoderLegion for more.