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.
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.
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.
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:
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!