DEV Community

TusharShahi
TusharShahi

Posted on

Cost of unnecessary Optional Chaining (and Nullish coalescing operator)

If you are a developer who has worked with JavaScript in recent years, I am sure you are well aware of the below syntax:

const a = obj1?.x; 
const b = obj2.c ?? '2'
Enter fullscreen mode Exit fullscreen mode

We see them all around us, making our lives easier.

The first one is optional chaining and the other one is the nullish coalescing operator.

Both of them are so easy to use that they have a huge adoption in a lot of code bases. They help protect us from the embarrassing situation of our webapp breaking in production. But there is a cost to their overuse which will make one debate their pros and cons.

Background

As we can see from the docs, both are recent additions to the language. This means to get them to work on older browsers, they need polyfills.

We can open our dev tools and write something like window?.testing and it will not complain. But if we do the same on an older browser, it will throw us an error.

For our nice new syntax to work on old outdated browsers, it is converted into (probably) longer and more primitive code. This whole logic is what a polyfill is. It is a script that updates/adds new functions.

Babel

So, how is this working out in the case of our React apps. We never have to worry about writing some polyfill. The reason for that is Babel. Babel, a transpiler, converts modern JavaScipt code to make it easily understandable to most browsers. We get to decide the target browsers we plan on supporting and Babel does the conversion.

Your code might not use Babel but some other transpiler like SWC, but the funda is common. Convert new-fashioned code into code that most browsers can understand.

All this is part of the bundling process, with the final aim of converting our React code into a group of JS, CSS and HTML files.

Being extra cautious

Coming to the topic that encouraged me to write this blog. I was reviewing a code and saw something like below:

      if (cityName) {
        if (location === cityName)
          res.writeHead(301, {
            Location: `/somegibberish/${cityName?.toLowerCase()}/`,
          });
        else
          res.writeHead(301, {
            Location: `/somegibberish/${cityName?.toLowerCase()}/${localitySlug}`,
          });
      }
Enter fullscreen mode Exit fullscreen mode

I pointed out the problem: cityName is checked once at the top level, and inside the if branch, optional chaining is used to check the (somewhat) same thing.

The response: Although unnecessary, there is no harm in doing that.

The first harm I could see was in setting the wrong expectations. If the above code block became much larger with multiple contexts like function calls, it would be hard to track whether cityName can be falsy. (This is a problem, more specific to JS-only code bases)

The second is something I found out only when looking at the bundle output.

Transpiling

The code written above will not go as it is into our bundle. As already established, it will get converted into old-school JS so we can target as many browsers (in turn as many users) as possible.

Here is the screenshot of what optional chaining and nullish coalescing operator code gets converted into.

Input of the code

Output of the code

Let us pick any one of them.

Nullish Coalescing Operator: a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.

Something like x ?? '2' gets converted into x !== null && x !== void 0 ? x : '2'.

void 0 is not something you see every day. It is the old way of writing undefined. It is favored over directly writing undefined for 2 reasons:

  1. In older JS specifications, undefined was not a reserved word. One could literally do var undefined = 'defined'. (This is no longer a problem)

  2. undefined takes more bits than void 0. Which definitely is a good enough reason to use it, when our bundle will be filled with it.

Cost to the bundle

Here is how the above code snippet, sat in our code bundle:

The output of the above code

The above gives a sense of the problem with using optional chaining and NCO (in short :P) blindly, even more so in situations where they are not required. The bundle size can increase massively, even after modification.

I would still use the above two features, but I strongly advise not to use them unnecessarily now that I have seen the bundle output.

Bonus: Here is an es-lint rule that might help: no-unnecessary-condition in pruning out all those unnecessary checks.

Top comments (0)