Basti Ortiz

Posted on

# The Semantics of Falsy Values

I recently read this article by @nunocpnp about the technical differences between `null` and `undefined` in JavaScript. After reading, I realized how the semantics of falsy values can be easily dismissed and overlooked. The very fact that these two constructs are often confused, or interchanged in the worst cases, means that there is a need to be more informed about the subtle and nuanced semantics behind falsy values.

In this article, I will discuss just that. I wish to expound on @nunocpnp's article by accompanying the technical differences of falsy values with their the semantic context. By the end, we can all be better informed about the proper usage of the many falsy values in JavaScript.

Of course, this doesn't mean that everything that I will discuss strictly applies to the JavaScript language only. Other languages have their own falsy constructs but similar—if not the same—semantics.

Without further ado, let's begin with the simplest and most straightforward falsy value: `false`.

# `false`

The boolean `false` is used to communicate when a boolean condition is not met. Its usage is most appropriate for checks and guard clauses, where a condition can only be either `true` or `false`—nothing more, nothing less.

# Zero (`0`)

The integer `0` must only be used in numerical operations or—in rare, low-level cases—for bitmasking. The number `0` is always a numerical construct. Therefore, using it as a boolean construct is semantically incorrect and strongly discouraged.

``````// This is good.
function square(num) { return num * num; }

// This is semantically incorrect because the function
// is a boolean condition that checks if a number is odd.
// By interpreting the numerical result of the modulus
// operator as a boolean value, this violates the
// semantics of the `isOddNumber` function.
function isOddNumber(num) { return num % 2; }

// This can be improved by explicitly converting
// the return value to a boolean.
function isOddNumber(num) { return Boolean(num % 2); }

// This also works like the previous example,
// but it looks pretty "hacky" to be completely
// honest. The "double negative operator" uses implicit
// type coercion under the hood, which is not exactly
// desirable if we want our code to be readable,
// maintainable, and semantically correct.
function isOddNumber(num) { return !!(num % 2); }
``````

# Not a Number (`NaN`)

The same logic goes for `NaN`. The value `NaN` is strictly used to indicate failed numerical operations. It can be used as a boolean value to check if a numerical operation is valid. However, it cannot be used as a reckless substitute for the boolean primitives `true` and `false`.

``````// This is good. There is no need to explicitly
// convert `NaN` to `false` because the function
// is a numerical operation that works fine except
// for a few edge cases (when y = 0). Semantics is
// still preserved by the returned number or `NaN`.
function divide(x, y) { return x / y; }

// This is semantically incorrect because `NaN` is
// recklessly used where `false` is already sufficient.
function canVoteInElections(person) {
return (person.age > 18 && person.isCitizen)
? true : NaN;
}
``````

# Empty Arrays (`[]`) and Empty Strings (`''`)

Although empty arrays are in fact not falsy values as ruled by the language specification, I still consider them to be semantically falsy, if that makes sense. Furthermore, since strings are technically just arrays of individual characters, then it follows that an empty string is also a falsy value. Strangely enough, an empty string is indeed a falsy value (according to the aforementioned section in the language specification) despite an empty array being otherwise.

Nonetheless, empty arrays and empty strings as is are not to be implicitly interpreted as boolean values. They must only be returned in the context of array and string operations.

For instance, an empty array can be returned if an operation just happens to filter out all of its elements. The `Array#filter` function returns an empty array if all elements of a given array meet a certain filter condition. After applying a filter that happened to eliminate all elements, it simply makes more sense to return an empty array instead of some other falsy value like `false` or `NaN` because the resulting empty array implies that it has been filtered from a previous array.

A full toy box can serve as a relevant analogy. The toy box represents an array. The act of removing all toys from the toy box represents the filtering process. After a filtering process, it makes sense to be left with an empty toy box.

However, if one truly insists to interpret an array as a boolean type based on whether or not it is empty, it is desirable to use the `Array#length` property. However, since it returns an integer value, a semantically correct—albeit rather pedantic—implementation requires an explicit conversion to a boolean primitive.

``````// This is semantically correct.
function isEmptyArray(arr) { return !Boolean(arr.length); }

// This is also correct, but without the indication
// of an explicit conversion, this has lesser semantic
// meaning than its unabbreviated variation above.
function isEmptyArray(arr) { return !arr.length; }

// This is okay...
function logEmptyString(str) {
if (!str)
console.log(str);
}

// ... but this is better.
function logEmptyString(str) {
if (str === '')
console.log(str);
}
``````

# Empty Objects (`{}`) and `null`

Just like empty arrays, empty objects are considered to be "truthy" by the language specification. For the sake of this article, I will also consider them as semantically falsy.

Empty objects follow the same reasoning as empty arrays. They can only be returned as a result of some object operation. They cannot be used as reckless substitutes for boolean primitives.

Fortunately, there exists a falsy boolean construct that literally means nothing: `null`. If an object operation results in an empty object, it is sometimes more appropriate to return `null`.

For instance, a function that searches a collection of objects can return `null` if it fails the search. In terms of semantics, it makes more sense to literally return nothing than an empty object. Additionally, since all objects are truthy while `null` alone is falsy, such a search function can circumvent explicit boolean conversions. An example of a semantically correct object search function is `document.getElementById`.

Concisely put, the semantics of `null` revolves around the fact that it is a deliberate and explicit representation of absolutely nothing. One can think of it as an "emptier" object than an empty object. In this light, it suddenly makes more sense why `typeof null` returns `'object'` even though it was a mistake to begin with.

# `undefined`

As its name suggests, `undefined` is strictly a placeholder for something that hasn't been defined in the program, whereas `null` is a placeholder for something that doesn't exist whatsoever.

If one was to deliberately return `undefined` from an object search function, it defeats the semantics of `null` which communicates express intent of returning absolutely nothing. By returning `undefined`, the search function in question returns something that hasn't been defined rather than something that doesn't exist.

To put it more concretely, let's suppose that `document.getElementById` returns `undefined` if an HTML element with the given ID doesn't exist in the current `document`. Wouldn't that sound rather strange?

It is for this reason why `null` is more correct and desirable than `undefined` when returning nothing. Although the two basically mean the same idea of nothingness, subtleties in the language completely change their semantics.

# Conclusion

Semantics is a particularly irking subject in programming because it doesn't significantly affect the behavior of a program, yet it plays a huge role in the readability and maintainability of code.

As illustrated by `null` and `undefined`, two constructs can be semantically different despite representing the same idea. It is for this reason that we must be aware of these nuances in order to write more consistent and maintainable code.

As a general rule of thumb, falsy types must be used and returned in the correct context. Relying on implicit type coercion is discouraged because it does not respect the semantics of data types. When converting types, especially those that are boolean by nature, it is always semantically better to explicitly convert them.

emptyother
1. If null means something doesn't exist, is there a reason for a function to ever return `undefined`? If I ever got an `undefined` back from a function, should I assume something went wrong and throw an error?

2. I would need to take existing values, check them for null, then create new undefined variables if i want to make use of ES6 default parameters. Seems messy.

It seems to be no drawbacks to just use `undefined` instead of `null` everywhere. Less code and easier to follow variables.

Basti Ortiz • Edited
1. Yes, there actually is. Map#get returns `undefined` when you attempt to access a key value that hasn't been defined. You can explicitly set a key to `null` and have it return `null`, though, but would be intentional on the programmer's part. I wouldn't consider this to be some error. I think it's rather intuitive. It's consistent with how objects return `undefined` when a key/property hasn't been defined in itself and its prototype chain.
2. I'm not quite sure if I follow what you mean by this. Can you please rephrase it? Or provide a concrete example?

Well, as said in the article, it is true that there aren't really any drawbacks for choosing `null` over `undefined`, but it sure does help in keep code consistent with the languages's semantics, which in turn potentially results in more maintainable code.

Ben Calder • Edited

#2 is presumably referring to the issue that passing `null` to a function with a default argument does not result in the default being used:

``````function returnNull() {
return null;
}

function returnExplicitUndefined() {
return undefined;
}

function returnImplicitUndefined() {
return;
}

function functionWithArgumentDefault(text = 'something') {
console.log(text);
}

function functionWithArgumentDefaultAndNullFallback(text = 'something') {
// suddenly default args don't seem so useful
const output = text !== null ? text : 'something';
console.log(output);
}

functionWithArgumentDefault(returnNull()); // null
functionWithArgumentDefault(returnExplicitUndefined()); // 'something'
functionWithArgumentDefault(returnImplicitUndefined()); // 'something'
functionWithArgumentDefaultAndNullFallback(returnNull()); // 'something'
``````

Of course it's possible that you may want to output something different in the case of a `null` input; but this is definitely an issue that can catch people out...

Basti Ortiz

I'm not quite sure if the snippet you attached is correct. Did you mean to invoke the function `baz` in the last line of the snippet?

Ben Calder

Yes. Did you try running the code? It demonstrates precisely what I described. It's not uncommon to use the return value from one function as the calling argument in another. In this case returning `null` negates the usefulness of the default argument; since you still need an explicit check for `null` in the function body if you want to return the default.

I'll update the function names to make the example clearer; and add another possibility ;)

Basti Ortiz • Edited

Anyway, I would argue that that's correct and reasonable behavior because `null` was explicitly passed in as an argument, whereas passing `undefined` as an argument would implicitly call upon the default value to be used as the definition in stead.

This goes back to what I said in the article where `null` communicates the express intent to pass absolutely nothing, whereas `undefined` communicates the absence of a definition. `null` does not, in fact, negate the usefulness of the default value if used correctly in this context.

So, really, the only issue in this code snippet is its noncompliance with the semantics of the language, which is what brings about the "issue" you presented in the first place.

Ben Calder • Edited

I agree that passing `null` to a function does show an explicit intent that is different to not passing an argument... But I think the original question - and certainly my example code - relate to whether a function should return `null` or `undefined` to represent a falsy value; given that returning `null` then makes it impossible to leverage default arguments. You seem to be suggesting that `null` is always more appropriate.

I'd tend to use `null` if the failure of a function to return something is going to block further execution; so yes - in the context of `document.getElementById` it makes sense since you presumably want to do something with that element once you retrieve it and there isn't going to be an obvious default. But in other contexts the returned value may not be essential for things to keep running; in which case I'd argue that `undefined` is totally appropriate. Of course, right now, I can't think of a good example :shrug:

Basti Ortiz • Edited

Okay, your argument is much clearer to me now. Thanks for the clarification. I now see the issue of returning `null` in this case.

For me, I'd still like to believe that even though the default parameter has been rendered useless due to the return value of `null` (as you presented in the snippet), the omission of the default value is still is semantically correct given that an intentional `null` was passed as an argument as the definition of the parameter.

However, I do agree about how it has basically made the default value useless, especially if one expects JavaScript to "fall back" to a default value if the argument passed in is a falsy value.

To appease both of our equally valid arguments, I'd say that we should refrain from using defaulted parameters as mere "fallbacks" to falsy values. For me at least, semantically speaking, the default parameter has always been there to allow a function to be executed despite having some of its parameters not being defined upon invocation. By restricting ourselves to only using default parameters when an invocation lacks all arguments, we can avoid the issue you presented since `null` would never be treated as a falsy value that needs a "fallback" in the first place (since `null` is the definition itself).

But I can see how my compromise may seem unsatisfying, to be honest. To that I say, "to each its own". Each of us has our own coding styles and philosophies. For me, I would never treat `null` as a falsy value that needs a default because I interpret `null` as a sufficient definition in itself.

TL;DR: `null` is not supposed to have a "fallback" in the first place.

Ben Calder

Agreed: I think I already said that it makes sense for `null` not to have a fallback ;)

Anyway, I had time to think of some examples to illustrate when it may make more sense to return `undefined` from a function:

• a helper function to select an optional property that may be deeply nested on an object. If I try and directly access an object property that doesn't exist I get `undefined`. It makes more sense to me to also return `undefined` from my helper function than to explicitly return `null`
• a function that processes some form of optional input. If input is not provided it is `undefined`. In this case I'd be inclined to also return `undefined`

In both these cases it may be desirable to allow another function that accepts these return values to provide a default fallback.

To be clear: these comments aren't intended as criticism. I found your article interesting; especially since misunderstanding of falsy values and coercion often lead to unintended errors - e.g. the common use of the shortcut `if(someValue)` to determine that a value is defined falls flat when dealing with numerical input that may be 0...
But I also thought @tom 's question was justified. I think the confusion comes from this line in the article:

It is for this reason why null is more correct and desirable than undefined when returning nothing.

Was that intended to be meant specifically in the context of an object search; or as a general directive for returning nothing from any function? I'd disagree with the latter assertion for the reasons outlined above: I'd argue that context is more important in determining what is a semantically correct falsy value to return than having a rule that is set in stone: "never say never" ;)

Basti Ortiz • Edited

Oh, no! Don't worry about it. I take no offense because I know that this is an educated discussion. 😉

As for the examples you provided, I definitely agree with them. I think it semantically makes sense to return `undefined` in your examples since the values of the fields haven't been defined in the first place. In other words, what I'm trying to say that your usage of `undefined` is truly valid and justifiable in those specific contexts.

In regard to the quote you cited, I see now how my wording can be misinterpreted and may have implied that `undefined` is "always" wrong. That is my mistake. When I wrote the sentence you quoted, what I meant by it is that `null` should always be returned if the intent is to literally pass absolutely nothing.

YuanHao Chiang • Edited

```function isOddNumber(num) { return num % 2; } function isOddNumber(num) { return Boolean(num % 2); }```

In my case, I find that:

`function isOddNumber(num) { return (num % 2 !== 0); }`

Is even more clear than an explicit Boolean conversion. I'm also not a big fan of !!value, as when codebase grows, it becomes hard to skimp through the code.

Cheers! :)

Basti Ortiz

Yes, that definitely works, too! However, I believe you meant `num % 2 !== 0`, though, since we're checking if a number is odd.

Either way, I get your point. That's a valid way of thinking about it rather than the explicit and rather bulky `Boolean` call.

YuanHao Chiang

Ooops, yes, edited my post :)

marcellothearcane

`!!` is faster: jsben.ch/yVcTI

Also it warns about using `Boolean()` as a constructor

Basti Ortiz • Edited

That's just a microoptimization to be completely honest.

And I never used `Boolean` as a constructor. If I did, I would've used the `new` keyword. The way I used `Boolean` is merely a normal function call (like any other function call) to explicitly convert a value to a boolean. It's vaguely similar to how `Object#toString` converts any object to a string.