In JavaScript, it's common to see code like this:
const color = options.color || 'red';
Or like this:
const name = data && data.name;
You might know what this code does, but maybe you don't know why. A couple of simple but very clever core language mechanics enable us to write code like this. Despite the clickbait title, there is (sadly) no magic involved. Let's take a look at what's really going on here.
In case you don't know what this code does: the former snippet provides a default value for the color
variable. If options.color
is a truthy value (such as a non-empty string), then color
gets set to that value. But if options.color
is a falsy value, such as undefined, then color
gets set to the default value of 'red'
.
The latter snippet guards against data
being undefined (or null). If data
is undefined, then trying to reference data.name
directly will result in an error being thrown. But if we use the &&
trick, there will be no error in such a case. name
will just be set to undefined.
Boolean Expressions and Short-Circuit Evaluation
To understand how this works, it's important to first understand short-circuit evaluation.
In a language like JavaScript, short-circuit evaluation means that when a JavaScript engine evaluates a boolean expression (such as a || b
), it will only evaluate as much of the expression as it needs to, in order to find the answer.
In the expression a || b
(meaning a
OR b
), think logically about what happens if a
is true
. If a
is true, then it doesn't even matter what b
is, right? b
could be true
, false
, or anything else, and the result of a || b
will be true regardless. If a
is true, then a
OR b
is also true, period.
JavaScript engines are smart enough to know this too, and in practice it means that if a
is true
(or any other truthy value), the engine won't even bother looking at b
.
To see this in action, you could try running the following code:
console.log(true || null.abc);
While it's illegal to try to reference a property on null
, this code does not error, because the engine never evaluates the right-hand-side of the expression! It doesn't need to, because the left-hand-side is sufficient to answer the question on its own.
Boolean Operators Don't Return Boolean Values!
The second piece of the puzzle is what's returned by boolean operators, such as ||
and &&
. Often, developers assume that these operators return boolean values, but that's actually not the case.
Try running this code:
console.log('a' || 'b');
It does NOT return true
. It returns 'a'
!
And this code:
console.log('a' && 'b');
returns 'b'
!
What's going on here? This is the part that I find really clever. Boolean expressions do not return boolean values, they actually return the last thing that was evaluated.
Thanks to short-circuit evaluation, when evaluating the expression 'a' || 'b'
, the engine only evaluates the left-hand-side of the expression. That means that 'a'
is the last thing that it evaluates in this expression. Therefore, the return value of the expression is 'a'
.
In the expression 'a' && 'b'
, the left-hand-side of the expression is truthy, but because this is an AND
operation, the engine still has to look at the right-hand-side to check if it's truthy or not. If it is, the expression is true. If it's not, then the expression is false. That means in this case, the final thing that's evaluated in this expression is 'b'
. That's why 'b'
is the return value of the expression.
Putting it Together
That's the theory, but we can boil it down into simpler-sounding rules that might make it easier to use in practice:
- The
||
operator returns the thing on left-hand-side, if that thing is truthy. If it's not, it returns the thing on the right-hand-side. - The
&&
operator returns the thing on the left-hand-side, if that thing is falsy. If it's not, it returns the thing on the right-hand-side.
Let's look at the examples from the top of the article again:
const color = options.color || 'red';
Applying rule #1, if options.color
is truthy, the expression returns options.color
. Otherwise, it returns 'red'
.
const name = data && data.name;
Applying rule #2, if data
is falsy, the expression returns data
. Otherwise, it returns data.name
.
Getting Fancy
You can get pretty fancy with this. For example, if you want to look at a property deep inside of an object, and provide a default value if it's not there, you could write code like this:
const name = (data && data.profile && data.profile.name) || 'Name Unknown';
If data
is truthy, it will return the thing on the right. The thing on the right is yet another boolean expression, where if data.profile
is truthy, it too will return the thing on the right. If any of this is falsy, then (data && data.profile && data.profile.name)
will return the last thing it evaluated. Since that thing would be falsy, ||
will return the thing on the right-hand-side, 'Name Unknown'
.
Does that sound a bit convoluted? I hope it does, and I wouldn't recommend writing code like this. It can get pretty hard to understand. But if you do understand it, then congratulations, you understand the magic of JavaScript boolean expressions.
Optional Chaining and Nullish-Coalescing
As an aside, while code like the above used to be somewhat common in JavaScript, the new-ish optional chaining and nullish-coalescing features give us a simpler way to do the same thing:
const name = data?.profile?.name ?? 'Name Unknown';
We won't dive into that, but it deserves mention.
Conclusion
JavaScript engines only evaluate the parts of a boolean expression that they need to, and boolean expressions return the last thing that was evaluated. That's it. It's a simple mechanism, but it enables some pretty magical-looking constructs. Hopefully this article helped unwind the mystery, and if you have any questions or comments please post them below!