Nesting code is a common feature in a programming language. While it has some advantages, it is mainly considered an anti-pattern. There are a number of problems when nesting code:
- The code is hard to read
- The context is hard to understand
- Hard to maintain, whilst developers will try to avoid it
Let's explore different ways to invert nested conditions and make code easier to work with.
Executing functions
Inverting conditions
Consider the following example code:
function payBill(user = { isLoggedIn: false }, amount = 0) {
if (user) {
if (user.isLoggedIn) {
if (amount > 0) {
// pay the bill
} else {
throw new Error("Invalid amount");
}
} else {
throw new Error("User needs to login");
}
} else {
throw new Error("User doesnt exist");
}
}
This code has a nesting depth of 3 and this will probably continue if we need to cover different validation scenarios. Simply by inverting the conditions we can reduce the nesting depth to 1 as follows:
function payBill(user = { isLoggedIn: false }, amount = 0) {
if (!user) {
throw new Error("User doesnt exist");
}
if (!user.isLoggedIn) {
throw new Error("User needs to login");
}
if (amount <= 0) {
throw new Error("Invalid amount");
}
// Pay bill
}
This code will work exactly the same way as the first example. The only difference is that it is easier to read, understand and maintain.
Early return
This is another solution that can be used in function as a guard clause before executing the function.
For example, instead of wrapping everything in a condition like this:
function doSomething(myParam) {
if(myParam){
// Do something here
}
}
You can simply revert the condition and stop the execution of the function when the condition is not met.
function doSomething(myParam) {
if(!myParam){
return;
}
// Do something here
}
Loops
Building custom logic within loops is something common that we all do. We can definitely improve the way we execute loops by reducing the nested conditioning.
Here is an example with nested conditions:
const myList = ["one", "two", null, "four", "", "six"];
for (let i = 0; i < myList.length; i++) {
const item = myList[i];
if (item !== null) {
if (item !== "") {
// Do something with current item
console.log(item);
}
}
}
Here is an improved version:
const myList = ["one", "two", null, "four", "", "six"];
for (let i = 0; i < myList.length; i++) {
const item = myList[i];
if (item === null) {
continue;
}
if (item === "") {
continue;
}
// Do something with current item
console.log(item);
}
By using the continue
statement, the loop stops the current iteration of the loop and continues with the next iteration. This stops the nested conditions and makes the flow more linear, either stopping the iteration to go further down the block or continue with the next item.
The same can be done with .forEach
loop.
const myList = ["one", "two", null, "four", "", "six"];
myList.forEach((item) => {
if (item === null) {
return;
}
if (item === "") {
return;
}
// Do something with current item
console.log(item);
});
The return
statement plays the same role as continue
statement in for
loop.
Conclusion
Minimizing nesting and applying these simple tricks can make your life much easier. It's not only going to make the code more readable and understandable, but it will also improve your coding style. Your teammates will thank you for that later.
Top comments (0)