The Costs of Optional Chaining

Optional chaining has some hidden costs

Optional chaining has reached stage three and it’s time for a reevaluation.

A little more than a year ago, we decided to start using @babel/plugin-proposal-optional-chaining. As usual with babel plugins, the primary reason was developer experience: "It will make our lives easier".

And it did. It still does. I see it being used everywhere, throughout our codebase.

In react componentDidUpdate:

And in render functions:

It looks nice and it’s easy to understand what’s going on.

Yet it comes at a cost to both performance and bundle size. And we, or at least I, seriously underestimated this cost.

Performance

Let’s get performance out of the way, because that’s not what concerns me most.

The performance cost is there if optional chaining is being overused. Don’t guard all your properties — only guard the unknowns. It’s safe to make assumptions of existence if you’re dealing with your own code.

That being said, we aren’t iterating our own render function 65 million times a second. So even while the performance hit can be up to 45%, it can still be negligible in production environments.

Let’s move on.

Bundle Size

The CommentButtons component posted above contains 244 bytes of written code, which is transpiled into 1.000 bytes. Larger by a factor of four.

Because it’s our own code, we can safely assume that whenever the user prop is not undefined, it also has the can property. If it isn’t enforceable by the backend it would be enforceable by the frontend, a parent component, or the place where we call the API.

Anyway, we can reduce the transpiled byte size to 477 bytes by rewriting that component to remove the optional chaining. We’re not even assuming the existence of can here, we default it to an empty object instead.

I realize this is an extreme example. But I see code quite similar to this in the wild. We developers just love our productivity tools. And if there’s a babel plugin that makes something easier, why not use it?

I’m not saying to not use the optional chaining at all. I still love using it. I’m asking you to remember that it comes at a cost. For example, try to not use a fallback for the same property twice within a single method:

We can easily reduce that, by only checking the user.can property once:

And unless your first optional operator is nested somewhere, it might be worth it to take that last step, and avoid the optional operator at all:

I hope this makes my point. I do realize that gzip can remove some of the overhead, as it’s quite good at compressing repeating patterns like === void 0 and === null. But even with gzip, the costs of optional chaining are there. Please remember it, as we’ll be stuck using the babel transpiler for quite some time. Even now it's stage three, it will not land in every browser that we need to support in the short term.

I’ll keep using optional chaining…albeit less fanatically!