Conditional statements are an essential part of JavaScript. They let you execute code based on whether a given condition is true or false, and you can nest multiple elseif statements (and an else) to evaluate more than one condition.

But here's the problem—when writing complex if...else chains, things can quickly get messy, and you can easily end up with code that's difficult to read and understand.

Let's learn how to refactor long and complex if...elseif...else conditional chains into a more concise, cleaner and easier-to-understand version.

Complex if...else Chains

When writing complex if...else statements in JavaScript, it's essential that you write clean, concise and understandable code. For instance, take a look at the if...else conditional chain inside the function below:

        function canDrink(person) {
  if(person?.age != null) {
    if(person.age < 18) {
     console.log("Still too young")
    } else if(person.age < 21) {
      console.log("Not in the US")
    } else {
      console.log("Allowed to drink")
    }
  } else {
    console.log("You're not a person")
  }
}

const person = {
  age: 22
}

canDrink(person)

The logic here is simple. The first if statement ensures that the person object has an age property (else he or she isn't a person). Inside that if block, you've added an if...else...if chain that basically says:

If the person is younger than 18, they're too young to get a drink. If younger than 21, they're still below the legal drinking age in the United States. Otherwise, they can legally get a drink.

While the above code is valid, the nesting makes it harder for you to understand the code. Luckily, you can refactor the code to be concise and easier to read by using a guard clause.

Guard Clauses

Anytime you have an if statement that wraps all of your code, you can use a guard clause to remove all the nesting:

        function canDrinkBetter() {
  if(person?.age == null) return console.log("You're not a person")

  if(person.age < 18) {
    console.log("Still too young")
  } else if(person.age < 21) {
    console.log("Not in the US")
  } else {
    console.log("Allowed to drink")
  }
}

At the start of the function, you defined a guard clause stating that if that specific condition isn't met, you want to exit the canDrinkBetter() function immediately (and log "You're not a person" on the console).

But if the condition is met, you evaluate the if...else chain to see which block is applicable. Running the code gives you the same result as the first example, but this code is easier to read.

Don't Use a Single Return

You might argue that the above technique isn't a good programming principle because we're using multiple returns in the same function, and you believe it's better to have just one return statement (aka, single return policy).

But this is a terrible way to write code because it forces you into the same crazy nesting situations we saw in the first code sample.

With that said, you can use multiple return statements to further simplify your code (and get rid of the nesting):

        function canDrinkBetter() {
  if(person?.age == null) return console.log("You're not a person")

  if(person.age < 18) {
    console.log("Still too young")
    return
  }

  if(person.age < 21) {
    console.log("Not in the US")
    return
  }

  console.log("Allowed to drink")
}

This code works the same as the two previous examples, and it's a little bit cleaner as well.

Extract Functions for Cleaner Code

Our last code block was cleaner than the first two, but it's still not as good as it could be.

Instead of having a long if...else chain inside one function, you can create a separate function canDrinkResult() that does the check for you and returns the result:

        function canDrinkResult(age) {
  if(age < 18) return "Still too young"
  if(age < 21) return "Not in the US"
  return "Allowed to drink"
}

Then inside the main function, all you need to do is first apply the guard clause before calling the canDrinkResult() function (with the age as its parameter) to get the result:

        function canDrinkBetter() {   
  if(person?.age == null) return console.log("You're not a person")

  let result = canDrinkResult(person.age)
  console.log(result)
}

So in this case, you delegated the task of checking the drinking age to a separate function and only called it when needed. This makes your code concise and more straightforward to work with than all the previous examples.

Keep else Away From Conditional Statements

You've learned how to refactor complex, nested conditional chains into shorter, easier-to-read ones using guard clauses and the function extraction technique.

Try to keep the else statement away from your conditionals as much as possible by using both guard clauses and the function extraction technique.

If you're still new to using the JavaScript if...else statement, start with the basics.