DEV Community

AkaraChen
AkaraChen

Posted on

Optimize the use of if-else, you need a better selective structure!

I don't know if you've ever encountered an if-else nesting like a pyramid placed horizontally:

if (true) {
}

if (true) {
}

if (true) {
  if (true) {
    if (true) {
    }
  } else {
  }
} else {
}
Enter fullscreen mode Exit fullscreen mode

If there are too many if-else in a piece of code, the readability of the code will drop rapidly, it becomes very difficult to maintain the code later on.Next I will talk about how to remove them.

Conditional operator

For the simpler if-else statements, they can easily be rewritten as conditional operators:

// Bad πŸ˜₯
if (true) {
  console.log("Congratutions!")
} else {
  console.warn("Oops, something went wrong!")
}

// Great πŸ₯°
true 
  ? console.log("Congratutions")
  : console.warn("Oops, something went wrong!")
Enter fullscreen mode Exit fullscreen mode

Of course, at this point the difference between the two is not that great.

Logical AND operator

If you just want to execute a function when a certain condition is met, then you can use logical AND operator:

if (true) {
  alert(1)
}

// is equals to:

true && alert(1)
Enter fullscreen mode Exit fullscreen mode

It has very strict conditions of use, only functions can be executed and no statements can be used, including "return". This doesn't make the code look more logical though, but it can be useful in certain cases.

Early returns

Metioned by @Edenn Touitou and @Infinite Table.

Just sort the conditions by their complexities and return each time:

function handleRequest(req) {
  if (req.code >= 500) {
    return "Server error";
  }
  if (req.code >= 404) {
    return "Cilent error";
  }
  return "Suceess";
}
Enter fullscreen mode Exit fullscreen mode

Table driven method

For code like the following, most of the time we will use switch instead. But it's not the best solution, and if we forget to add "break;", the code may run beyond expectations, and switch is not very elegant.

// Bad πŸ˜₯
const weekday = (num) => {
  if (num === 1) {
    return "Monday"
  } else if (num === 2) {
    return "Tuesday"
  } else if (num === 3) {
    return "Wednesday"
  } else if (num === 4) {
    return "Thursday"
  } else if (num === 5) {
    return "Friday"
  } else if (num === 6) {
    return "Saturday"
  } else if (num === 7) {
    return "Sunday"
  } else {
    return "Unknown"
  }
}

console.log(weekday(1)) // Monday
Enter fullscreen mode Exit fullscreen mode

This is the time to use the table driven method:

// Great πŸ₯°
const weekday = (option) => {
    let obj = {
        1: "Monday",
        2: "Tuesday",
        3: "Wednesday",
        4: "Thursday",
        5: "Friday",
        6: "Saturday",
        0: "Sunday"
    }
    return obj[option] ?? "Unknown"
}

console.log(weekday(1)) // Monday
Enter fullscreen mode Exit fullscreen mode

Or you can use ES6's Map:

// Great πŸ₯°
const weekday = (num) => {
  const map = new Map()
    .set(1, "Monday")
    .set(2, "Tuesday")
    .set(3, "Wednesday")
    .set(4, "Thursday")
    .set(5, "Friday")
    .set(6, "Saturday")
    .set(7, "Sunday");
  return map.has(num) ? map.get(num) : "Unknown";
};

console.log(weekday(1));
Enter fullscreen mode Exit fullscreen mode

Array's include method

In the previous section, we discussed how to optimize one-to-one selection structures, and here we discuss how to elegantly implement one-to-many selection structures.

For example, the following script:

const getContinent = (option) => {
  if (option === "China" || option === "Japan") {
    return "Asia";
  }
  if (option === "Germany" || option === "France") {
    return "Europe";
  }
};

console.log(getContinent("China"));
Enter fullscreen mode Exit fullscreen mode

It doesn't look that bad now because I haven't added all the countries in yet. This is certainly optimizable and can be easily avoided by using Array's include method:

const getContinent = (option) => {
  const Asia = ["China", "Japan"];
  const Europe = ["Germany", "Franch"];
  if (Asia.includes(option)) return "Asia";
  if (Europe.includes(option)) return "Europe";
  return "Unknown";
};

console.log(getContinent("China")); // Asia
Enter fullscreen mode Exit fullscreen mode

After this optimization, the code will not become cluttered even if more countries are added. But it can get even better:

const getContinent = (option) => {
  let [result, setResult] = ["unknown", (str) => (result = str)];
  const Asia = ["China", "Japan"];
  const Europe = ["Germany", "Franch"];
  Asia.includes(option) && setResult("Asia");
  Europe.includes(option) && setResult("Europe");
  return result;
};

console.log(getContinent("China"));

Enter fullscreen mode Exit fullscreen mode

Conclusion

From this article, we have learned how to reduce if-else in your code. If you think this post is helpful, please share it with your network, if you have comment, drop them anywayπŸ™‚.

Oldest comments (15)

Collapse
 
tamirazrab profile image
Tamir

I think table driven method should be must to teach during introductory teaching of programming fundamentals, students take these bad writing style along with them which is easy to spot on their code base. Sad thing is even most teacher which I've experienced aren't aware of that.

Collapse
 
akarachen profile image
AkaraChen

Yeah, sometimes I really wish my classmates would work on how to write beautiful code.

Collapse
 
fjones profile image
FJones

No. Please, no. Map lookups are such a silly shorthand, and more often than not they're just replacing a different structure designed for this purpose: switch/case.

Please stop telling people to use map lookups as if code golf is our goal.

Collapse
 
deozza profile image
Edenn Touitou • Edited

Simple thing to reduce the size and complexity of if-else loops : fail-fast.

You sort the conditions by their complexities and return each time. For example in place of writing :

if(response === 200) {

// ALL your business logic

}else if (response === 400){
}
else if(response === 403){
}
else if(response === 401){
}
Enter fullscreen mode Exit fullscreen mode

Write something more like this :

if (response === 401){
return
}

if(response === 403) {
return
}

if(response === 400) {
return
}

//BUSINESS LOGIC

return
Enter fullscreen mode Exit fullscreen mode

Code is less cluttered by unnecessary else-if and you understand better the goal of the function

Collapse
 
akarachen profile image
AkaraChen

This is indeed very useful, I forgot when I was writing it, I will add it in the next article revision

Collapse
 
hilleer profile image
Daniel Hillmann

THANK YOU. I wanted to write this exact thing but you came first

Collapse
 
thethirdrace profile image
TheThirdRace • Edited

Unpopular opinion: this structure is far from ideal for readability.

Before going any further, I want to mention that I 100% agree with the fail-fast methodology. You should always do that, but not at the cost of readability.

1 - Alphabetical order

Response statuses, or any code, should be in alphabetical order.

It's much easier to read when the codes are top -> down in a chronological order.

Trying to optimize execution time is totally pointless, you're not even gonna save 1 ms while going against any logical search pattern with your eyes...

If I need to CTRL + F or CMD + F to figure out which status is where, readability is 0%.

2 - Line indentation

Indentation improves readability a thousand fold.

Flattening your structure means you now have an implicit relationship with your ifs.

Given your eyes will scan top -> down first, then left -> right (assuming english), the if / else if / else pattern will give you immediate and explicit relationships between all the elements of your code.

3 - Multiple returns

Oh my, the most unpopular opinion ever...

Using multiple return in a function will force you to scan the whole code to figure out the exit points. The burden is put on the reader to map every single outcome by himself.

When using multiple return, every single if becomes a potential trap the dev could fall into and make a mistake. You absolutely need to scan all the code to figure out if the next block of code will be executed or not because at any point the eject button could be pressed.

If you use only 1 entry point and 1 exit point, you know by simply scanning top -> down that the next block of code will always be executed. All you have to do is follow indentation, the if / else if / else brackets and the whole outcome map will be laid out for you. You don't have to do any mental gymnastic, it's simple and you should very rarely make mistakes.

4 - Eye pattern

Just take a glance at those 2 patterns:

// Pattern A
xx (xxxxxx xxx xxx){
  xxxxxxxx xxxx xxxxx
}

xx (xxxxxx xxx xxx){
  xxxxxxxx xxxx xxxxx
}

xx (xxxxxx xxx xxx){
  xxxxxxxx xxxx xxxxx
}

xxxx xxxxxxx x xxxxxx
xxxx xxxxxxx x xxxxxx
xxxx xxxxxxx x xxxxxx

xxxxxxx
Enter fullscreen mode Exit fullscreen mode

Or

// Pattern B
xx (xxxxxx xxx xxx) {
  xxxxxxxx xxxx xxxxx
} xxxx xx (xxxxx xxx xxx) {
  xxxxxxxx xxxx xxxxx  
} xxxx xx (xxxxx xxx xxx) {
  xxxxxxxx xxxx xxxxx  
} xxxx {
  xxxxxxxx xxxx xxxxx  
  xxxx xxxxxxx x xxxxxx
  xxxx xxxxxxx x xxxxxx
  xxxx xxxxxxx x xxxxxx
}

xxxxxxx
Enter fullscreen mode Exit fullscreen mode

At a glance, can you tell me with 100% certainty that conditions in pattern A are mutually exclusive?

How about in pattern B?

With pattern A, you simply cannot know without any doubt the nature of those conditions. Maybe they're mutually exclusive, maybe they all apply, maybe it's anything in between.

With pattern B, you don't even have to know what's written, just look at the brackets and the indentation and you already know those conditions are mutually exclusive. Even after replacing every single letter by x, the meaning is still there and the pattern is still obvious.

Conclusion

I know a lot of people will go completely banana against this opinion. It's fine, I'm used to it.

All I'm gonna say is people need to take into account accessibility and readability. Both of which won't prevent you from failing fast/early and they won't impact performance in the slightest. There's really no reason to not account for them.

Collapse
 
get_infinite profile image
Infinite Table

if the conditions are exclusive, I find early returns to be a very useful way of organising code:

if (condition1){ 
   // do stuff when condition1 is true
   return
}
if (condition2){ 
   // do stuff when condition2 is true
   return
}

// do stuff for condition 3, which is the last condition

return ...
Enter fullscreen mode Exit fullscreen mode
Collapse
 
birowo profile image
birowo

you should try golang

Collapse
 
akarachen profile image
AkaraChen

Coincidentally, I'm about to learn golangπŸ˜‚

Collapse
 
wrench1815 profile image
Hardeep Kumar • Edited

I swear if i had a dollar for every minute i spend fixing when i used conditional operator wrong, I'd probably be a millionaire by now.
For some reason I always use it wrong and then wonder why. Then after half an hour I'm like 'oh!' πŸ˜‘

Nice article btw, liked, bookmarked, unicorned. Clicked on every button I could see so i could always save some time from now on πŸ˜‚

Collapse
 
volkmarr profile image
Volkmar Rigo

Maybe it's only me, but

if (true) 
  alert(1)
Enter fullscreen mode Exit fullscreen mode

or

if (true) alert(1)
Enter fullscreen mode Exit fullscreen mode

is easier to read and understand then

true && alert(1)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
thenickest profile image
TheNickest

I completely agree. Besides that I learned something new about the very shorthand syntax, Iβ€˜d always prefer the second one.

Collapse
 
akarachen profile image
AkaraChen

I thought so too, but many books claim that this style is not clear enough and could be misleading, so I didn't include it in the article

Collapse
 
well1791 profile image
Well

one more for the list

const someValue = [
  { isTrue: () => false, result: () => 'some false value' },
  { isTrue: () => true, result: () => 'some true value' },
].find(({ isTrue }) => isTrue())?.result()
Enter fullscreen mode Exit fullscreen mode

that could be used with tuples [() => false, () => 'some value'], with default values (setting the last item condition to return always true), etc..