Recently, I’ve been refactoring a lot of code at the company where I currently work. One of the most common issues I’ve encountered is deeply nested if/else/else if statements. They tend to grow like a large, tangled tree. The problem is that these structures are hard to read, difficult to reason about, and often include duplicate checks if not managed carefully.
In this tutorial, I’ll show you a few practical ways to refactor this kind of code and make it more readable and maintainable.
function getDiscount(user) {
if (user) {
if (user.isMember) {
if (user.purchaseHistory && user.purchaseHistory.length > 5) {
return 0.2;
} else {
if (user.coupon) {
if (user.coupon === 'VIP') {
return 0.15;
} else {
return 0.1;
}
} else {
return 0.05;
}
}
} else {
return 0;
}
} else {
return 0;
}
}
Tip 1: Taking “The Happy Path”
A simple but powerful technique to reduce nesting is to return as soon as you have enough information. This is done by adding guard clauses at the top of the function.
function getDiscount(user) {
if (!user) return 0; // guard: no user
if (!user.isMember) return 0; // guard: not member
if (user.purchaseHistory?.length > 5) return 0.2;
if (user.coupon === 'VIP') return 0.15;
if (user.coupon) return 0.1;
return 0.05;
}
This version is easier to follow because the function reads like a linear sequence of rules rather than a tree of nested conditions. Each check is isolated, making the code simpler to reason about and easier to test.
Tip 2: Map It Out to an Object
Another great way to replace long conditional or switch statements is by using an object lookup table. This approach separates the logic (the rules themselves) from the control flow.
const roleDiscount = {
guest: 0,
member: 0.05,
vip: 0.15,
staff: 0.25
};
function getRoleDiscount(role) {
return roleDiscount[role] ?? 0; // O(1) lookup; default 0
}
This code can replace long switch cases that can look like this:
function getRoleDiscountSwitch(role) {
switch(role) {
case 'guest': return 0;
case 'member': return 0.05;
case 'vip': return 0.15;
case 'staff': return 0.25;
default: return 0;
}
}
The object mapping approach not only makes the code shorter and more expressive but also provides the added benefit of O(1) complexity for lookups. Adding or modifying a role becomes as simple as updating the object, without touching the function logic.
Conclusion
Conditionals are one of the most common sources of complexity in everyday code. By applying techniques like guard clauses (“the happy path”) and object lookups, you can transform deeply nested logic into clean, testable, and extensible functions.
These are just a couple of my favorite refactoring techniques. What about you? Do you have other ways of simplifying complex conditionals in JavaScript? I’d love to hear your ideas — feel free to share them with me on LinkedIn!
🌟 Bonus Tip
I just found something really useful for learning refactoring — 🎧 Five Lines of Code (audiobook).
It’s honestly the simplest way I’ve come across to understand refactoring without getting overwhelmed.
The cool part is you can actually grab it for free with Amazon’s 30-day Audible trial, so you don’t pay a cent if you cancel before the trial ends. Here’s my affiliate link if you want to check it out: Five Lines of Code on Audible — I may earn a small commission if you sign up, at no extra cost to you.