The Strategy Pattern in JavaScript: Replace Messy If-Else Logic With Clean Code

The Strategy Pattern in JavaScript: Replace Messy If-Else Logic With Clean Code

Leader posted Originally published at dev.to 4 min read

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.


Step 1: Extract behaviors

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.

4 Comments

2 votes
1
2
2
1 vote
1 vote
1 vote

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelskiverified - Apr 23

Sovereign Intelligence: The Complete 25,000 Word Blueprint (Download)

Pocket Portfolioverified - Apr 1

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

5 Web Dev Pitfalls That Are Silently Killing Your Projects (With Real Fixes)

Dharanidharan - Mar 3

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

6 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!