DEV Community

Cover image for 5 ways to refactor if/else statements in JS functions
Sylwia Vargas
Sylwia Vargas

Posted on

5 ways to refactor if/else statements in JS functions

In this blog post I will present 5 ways to declutter your code getting rid of unnecessary if-else statements. I will talk about:


1. Default parameters

You know that feeling when you're working with inconsistent API and your code breaks because some values are undefined?

 let sumFunctionThatMayBreak = (a, b, inconsistentParameter) => a+b+inconsistentParameter

sumFunctionThatMayBreak(1,39,2) // => 42
sumFunctionThatMayBreak(2,40, undefined) // => NaN
Enter fullscreen mode Exit fullscreen mode

I see that for many folks the instinctive solution to that problem would be adding an if/else statement:

 let sumFunctionWithIf = (a, b, inconsistentParameter) => {
    if (inconsistentParameter === undefined){
      return a+b
    } else {
     return a+b+inconsistentParameter
    }
}

sumFunctionWithIf(1,39,2) // => 42
sumFunctionWithIf(2,40, undefined) // => 42
Enter fullscreen mode Exit fullscreen mode

You could, however, simplify the above function and do away with the if/else logic by implementing default parameters:

 let simplifiedSumFunction = (a, b, inconsistentParameter = 0) => a+b+inconsistentParameter

simplifiedSumFunction(1, 39, 2) // => 42
simplifiedSumFunction(2, 40, undefined) // => 42
Enter fullscreen mode Exit fullscreen mode

2. OR operator

The above problem not always can be solved with default parameters. Sometimes, you may be in a situation when you need to use an if-else logic, especially when trying to build conditional rendering feature. In this case, the above problem would be typically solved in this way:

let sumFunctionWithIf = (a, b, inconsistentParameter) => {
    if (inconsistentParameter === undefined || inconsistentParameter === null || inconsistentParameter === false){
      return a+b
    } else {
     return a+b+inconsistentParameter
    }
}

sumFunctionWithIf(1, 39, 2) // => 42
sumFunctionWithIf(2, 40, undefined) // => 42
sumFunctionWithIf(2, 40, null) // => 42
sumFunctionWithIf(2, 40, false) // => 42
sumFunctionWithIf(2, 40, 0) // => 42
/// 🚨🚨🚨 but:
sumFunctionWithIf(1, 39, '') // => "40"
Enter fullscreen mode Exit fullscreen mode

or this way:

  let sumFunctionWithTernary = (a, b, inconsistentParameter) => {
    inconsistentParameter = !!inconsistentParameter ? inconsistentParameter : 0
    return a+b+inconsistentParameter
}

sumFunctionWithTernary(1,39,2) // => 42
sumFunctionWithTernary(2, 40, undefined) // => 42
sumFunctionWithTernary(2, 40, null) // => 42
sumFunctionWithTernary(2, 40, false) // => 42
sumFunctionWithTernary(1, 39, '') // => 42
sumFunctionWithTernary(2, 40, 0) // => 42
Enter fullscreen mode Exit fullscreen mode

However, you could simplify it even more so by using the OR (||) operator. The || operator works in the following way:

  • it returns the right-hand side when the left-side is a falsey value;
  • and returns the left-side if it's truthy.

The solution could then look as following:

  let sumFunctionWithOr = (a, b, inconsistentParameter) => {
    inconsistentParameter = inconsistentParameter || 0
    return a+b+inconsistentParameter
}

sumFunctionWithOr(1,39,2) // => 42
sumFunctionWithOr(2,40, undefined) // => 42
sumFunctionWithOr(2,40, null) // => 42
sumFunctionWithOr(2,40, false) // => 42
sumFunctionWithOr(2,40, '') // => 42
sumFunctionWithOr(2, 40, 0) // => 42
Enter fullscreen mode Exit fullscreen mode

3. Nullish coalescing

Sometimes, however, you do want to preserve 0 or '' as valid arguments and you cannot do that with the || operator, as visible in the above example. Fortunately, starting with this year, JavaScript gives us access to the ?? (nullish coalescing) operator, which returns the right side only when the left side is null or undefined. This means that if your argument is 0 or '', it will be treated as such. Let's see this in action:

  let sumFunctionWithNullish = (a, b, inconsistentParameter) => {
    inconsistentParameter = inconsistentParameter ?? 0.424242
    return a+b+inconsistentParameter
}

sumFunctionWithNullish(2, 40, undefined) // => 42.424242
sumFunctionWithNullish(2, 40, null) // => 42.424242
/// 🚨🚨🚨 but:
sumFunctionWithNullish(1, 39, 2) // => 42
sumFunctionWithNullish(2, 40, false) // => 42
sumFunctionWithNullish(2, 40, '') // => "42"
sumFunctionWithNullish(2, 40, 0) // => 42
Enter fullscreen mode Exit fullscreen mode

4. Optional chaining

Lastly, when dealing with inconsistent data structure, it is a pain to trust that each object will have the same keys. See here:

  let functionThatBreaks = (object) => {
    return object.name.firstName
  }

  functionThatBreaks({name: {firstName: "Sylwia", lasName: "Vargas"}, id:1}) // ✅ "Sylwia" 
  functionThatBreaks({id:2}) // 🚨 Uncaught TypeError: Cannot read property 'firstName' of undefined 🚨 
Enter fullscreen mode Exit fullscreen mode

This happens because object.name is undefined and so we cannot call firstName on it.

Many folks approach such a situation in the following way:

  let functionWithIf = (object) => {
    if (object && object.name && object.name.firstName) {
      return object.name.firstName
    }
  }

  functionWithIf({name: {firstName: "Sylwia", lasName: "Vargas"}, id:1) // "Sylwia"
  functionWithIf({name: {lasName: "Vargas"}, id:2}) // undefined
  functionWithIf({id:3}) // undefined
  functionWithIf() // undefined
Enter fullscreen mode Exit fullscreen mode

However, you can simplify the above with the new fresh-off ECMA2020 JS feature: optional chaining. Optional chaining checks at every step whether the return value is undefined and if so, it returns just that instead of throwing an error.

  let functionWithChaining = (object) => object?.name?.firstName 

  functionWithChaining({name: {firstName: "Sylwia", lasName: "Vargas"}, id:1}) // "Sylwia"
  functionWithChaining({name: {lasName: "Vargas"}, id:2}) // undefined
  functionWithChaining({id:3}) // undefined
  functionWithChaining() // undefined
Enter fullscreen mode Exit fullscreen mode

5. No-else-returns and guard clauses

Last solution to clunky if/else statements, especially those nested ones, are no-else-return statements and guard clauses. So, imagine we have this function:

  let nestedIfElseHell = (str) => {
    if (typeof str == "string"){
      if (str.length > 1) {
        return str.slice(0,-1)
      } else {
        return null
      }
    } else { 
      return null
    }
  }

nestedIfElseHell("") // => null 
nestedIfElseHell("h") // => null
nestedIfElseHell("hello!") // => "hello"
Enter fullscreen mode Exit fullscreen mode

✨ no-else-return

Now, we could simplify this function with the no-else-return statement since all we are returning is null anyway:

  let noElseReturns = (str) => {
    if (typeof str == "string"){
      if (str.length > 1) {
        return str.slice(0,-1)
      }
    }

    return null
  }

noElseReturns("") // => null 
noElseReturns("h") // => null
noElseReturns("hello!") // => "hello"
Enter fullscreen mode Exit fullscreen mode

The benefit of the no-else-return statement is that if the condition is not met, the function ends the execution of the if-else and jumps to the next line. You could even do without the last line (return null) and then the return would be undefined.

psst: I actually used a no-else-return function in the previous example 👀

✨ guard clauses

Now we could take it a step further and set up guards that would end the code execution even earlier:

  let guardClauseFun = (str) => {
    // ✅ first guard: check the type
    if (typeof str !== "string") return null
    // ✅ second guard: check for the length
    if (str.length <= 3) console.warn("your string should be at least 3 characters long and its length is", str.length) 
    // otherwise:
    return str.slice(0,-1)
  }

guardClauseFun(5) // => null 
guardClauseFun("h") // => undefined with a warning
guardClauseFun("hello!") // => "hello"

Enter fullscreen mode Exit fullscreen mode

What tricks do you use to avoid clunky if/else statements?

✨✨✨ If you are comfortable with OOP JS, definitely check this awesome blog post by Maxi Contieri!


Cover photo by James Wheeler from Pexels

Latest comments (57)

Collapse
 
chitreshgoyal profile image
Chitresh Goyal

Can you help me refactoring dev.to/chitreshgoyal/refactor-java... code

Collapse
 
anu1411 profile image
anu1411 • Edited

Hi my code is like below, can u suggest how can I refactor this:

`if (Object.prototype.hasOwnProperty.call(schema.properties, key)) {
                var prop = schema.properties[key];
                if (object[key]) continue

               else if (prop.type === 'string' && !prop.enum) {
                object[key] = "";
                continue;
                }
               else if (prop.type === 'relationship') {
                object[key] = null; 
                continue;
                }
                else if (prop.type === 'array') {
                    object[key] = [];
                    continue;
                }
                else if (prop.type === 'object') {
                    object[key] = {};
                    continue;
                }
            }`
`


Enter fullscreen mode Exit fullscreen mode
Collapse
 
phongduong profile image
Phong Duong

JS has more useful features that make my life easier when writing conditions. Thank you

Collapse
 
peppermint_juli profile image
Juliana Jaime 🎃

I think after starting to use Typescript I haven't had most of these problems

Collapse
 
mtancoigne profile image
Manuel Tancoigne • Edited

I mainly develop in Ruby and that makes me love guard clauses. Even our linter enforces us to use them for the sake of readability, and it looks awesome :)

raise 'This is not possible' if impossible?

# code continues if not impossible?
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sylwiavargas profile image
Sylwia Vargas

Ruby 🥰 Guard clauses 🥰 Ruby Linters 🥰

Collapse
 
gene profile image
Gene

"Geeezz, skip all these and just use typescript."

:/

Collapse
 
detunized profile image
Dmitry Yakimenko

Oh my! Poor Javascript developers.

Collapse
 
marln94 profile image
marln94

I've been using guard clauses for a while but I didn't know they were called like that!
Great post 🎉

Collapse
 
sylwiavargas profile image
Sylwia Vargas

Thank you! Yeah they definitely ✨ spark joy ✨

Collapse
 
morgvanny profile image
Morgan VanYperen

oh hi! I didn't realize I knew who wrote the article until I got to the comments lol. good post!

Collapse
 
sylwiavargas profile image
Sylwia Vargas

hello! ✨ good to see you here! it's funny how the tech world is both big and small 😂

Collapse
 
afpaiva profile image
Andre Paiva

Hi Sylwia. Great article, congrats!

Let me do some noob questions?
Using [switch] in a situation with many [if/else if], will offer a better performance?
And is there difference by using [switch] or [if] or vice versa on a compiled or non compiled programming language? Like, on C it would be better this, and on JS better that?

Thanks a lot!

Collapse
 
sylwiavargas profile image
Sylwia Vargas

Hi @afpaiva ! Thank you for your question!
In comparison with if/else statements, switch statements are a bit faster with small number of cases and get incrementally faster with new conditions. Readability, it seems that devs prefer to read if/else for two conditions and then switches for more.
There's this great table from O'reilly:
performance table comparing switches, if/else and table lookups

Talking about performance in C is both beyond my expertise and comfort level, sadly!

Collapse
 
abelardoit profile image
abelardoit • Edited

Hi there,

I would opt to reject any non-numeric value to compute any calculus.

If you provide a non-number to compute a sum, then the precondition is not valid; therefore, my function would throw an exception.

Nice article! Thanks for bring it us! Best regards.

Collapse
 
_hs_ profile image
HS

Since your shorting out the code I expected something like

let noElseReturns = (str) => {
  if (typeof str == "string" && str.length > 1) {
      return str.slice(0,-1)
  }
  return null
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sylwiavargas profile image
Sylwia Vargas

yeah, that totally works too! Thanks for this comment!
I always try to balance how much I'm shortening the code vs how long the blog post is as my blogs posts are oftentimes read by folks with not much programming experience. Sometimes, I prefer to introduce changes incrementally or even not to introduce something because I also trust that the readers (just like yourself) would dig more or even find better solutions ✨

Collapse
 
kiddyxyz profile image
Muhamad Hudya Ramadhana

If you have more than one condition, why you just not using an array rather than or?

let arr = [null, undefined, '']
if (x in arr) {
   // code
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sylwiavargas profile image
Sylwia Vargas

Yeah it's one way to do it but that still keeps the same if-else (or just no-return-statement) logic that we want to ideally get rid of. Here I'd also worry about the performance as if-else is costly and so is looping and here we'd have looping inside if-else. But I like this example — thank you!

Collapse
 
vidhill profile image
David Hill

One thing I see on occasion,

is the resulting action being calling a function, with the only difference being some logic determining what a certain argument should be

const doSomething = function (arg1, arg2) {
  if (arg1 === "condition") {
    return callA(arg1, arg2, 10);
  }
  return callA(arg1, arg2, 0);
};
Enter fullscreen mode Exit fullscreen mode

versus

const doSomethingB = function (arg1, arg2) {
  const arg3 = arg1 === "condition" ? 10 : 0;
  return callA(arg1, arg2, arg3);
};
Enter fullscreen mode Exit fullscreen mode

Another one is calling functions with the same arguments, with the only difference being which function to call:

const doSomething = function (arg1, arg2, arg3) {
  if (arg1 === "condition") {
    return callA(arg1, arg2, arg3);
  }
  return callB(arg1, arg2, arg3);
};
Enter fullscreen mode Exit fullscreen mode

Versus

const doSomething = function (arg1, arg2, arg3) {
  const fnToCall = arg1 === "condition" ? callA : callB;
  return fnToCall(arg1, arg2, arg3);
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sylwiavargas profile image
Sylwia Vargas

Thank you for these examples! Coming from Ruby, this is something I definitely instinctively lean towards and catch myself writing sometimes 😂 I'll include you're examples in the blog post!

Collapse
 
merri profile image
Vesa Piittinen

Tip: instead of something === undefined && something === null you can write something == null and it matches both undefined and null, and only them.

I find it the only valid case to use == over ===.

Collapse
 
drarig29 profile image
Corentin Girard

That's true, but oftentimes you have a linter which forces to use "eqeqeq"

Collapse
 
merri profile image
Vesa Piittinen

Only if you have had someone strongly opinioned setting it up :) And even then you have the null: ignore option for it.

Collapse
 
sylwiavargas profile image
Sylwia Vargas

True! Thanks!