All posts
An overhead view of a developer's desk with three screens full of code, a keyboard and a mouse
Photo by ThisIsEngineering

Posted 4 min read

Why If Statements Don't Scale

A chain of conditions feels fine — until it doesn't. The problem was never the logic; it was the structure.

programmingsoftware-engineeringclean-codesoftware-architecture
On this page

I used to think the solution to complex logic was just more conditions. Until I realised the conditions weren’t the problem — the structure was.

The Pattern

We’ve all written something like this:

if (type === "A") { handleTypeA(); } else if (type === "B") { handleTypeB(); } else { handleUnknownType(); }

And at first, it feels completely fine. It’s readable, it’s simple, and it works.

But as more conditions and business logic get added, something starts to shift. The original intention becomes less clear. Small changes begin to feel riskier than they should.

It still works — but it becomes increasingly brittle and harder to change safely.

There’s Actually a Name for This

It’s called cyclomatic complexity.

In simple terms, it’s a way of measuring how many different paths your code can take. Every time you add another condition, you’re adding another possible path — another thing you need to understand, test, and keep in your head.

In small examples, it’s easy to ignore. But as the logic grows, so does the mental overhead.

Where It Starts to Break Down

The real issue shows up when this logic needs to change.

Adding a new type means going back into the function and modifying it directly:

else if (type === "C") { handleTypeC(); }

At first, this doesn’t feel like a problem. But over time, this function becomes a central place that everything depends on. Every change increases the risk of breaking something that already works.

You’re not just adding new behaviour — you’re modifying existing logic.

At some point I realised the problem wasn’t the conditions themselves. It was how the behaviour was structured.

A Different Way to Think About It

One way to approach this is to move the behaviour into a lookup object:

const handlers = { A: () => handleTypeA(), B: () => handleTypeB(), C: () => handleTypeC(), }; const handler = handlers[type] ?? handleUnknownType; handler();

Now instead of branching logic, you’re selecting behaviour.

Adding a new type doesn’t mean modifying the existing function — it means adding a new entry. The rest of the code stays untouched.

Is This the Only Solution?

No — and that’s worth saying clearly.

The important thing isn’t the specific pattern. It’s the shift in thinking.

Instead of asking: which path should this code take?

You start asking: how can I organise the behaviour so it doesn’t all live in one place?

Depending on the situation, you might:

They all solve a similar problem — separating behaviour instead of centralising it.

Trade-offs

This approach isn’t free, and it’s worth being honest about that.

A lookup object is great when the logic is straightforward — one type maps to one function. But if the conditions involve complex logic, multiple variables, or different combinations of state, a lookup can start to feel forced. At that point, other patterns like strategy or command might be a better fit.

There’s also a readability cost. A chain of if-statements is something every developer recognises immediately. A lookup object or pattern requires a little more context to understand at a glance — especially for someone new to the codebase.

So the question to ask isn’t “should I always avoid if-statements?” It’s “is this logic likely to grow, and will modifying it directly become a problem?” If the answer is yes, restructuring is probably worth it. If the answer is no, keep it simple.

Why This Matters

This approach changes how code evolves over time.

Instead of modifying existing logic every time something new gets added, you’re extending the system alongside it. That reduces the risk of breaking things and makes the codebase easier to reason about as it grows.

Final Takeaway

The issue was never whether the code works.

It’s whether it will still be easy to change when things — inevitably — do.

And that usually comes down to how the behaviour is structured, not the logic itself.


Originally published on Medium .

2026 © Jordan O’Hara