while.dev
5 min. read
Software Development

Null vs 0 vs undefined

Explaining truthy, falsy, null, 0, and undefined in TypeScript

What is a Truthy or Falsy value?

Every possible variable value of any type will be implicitly converted to a boolean true or false value if needed. The need to do so is usually caused by an if-statement, having a non-boolean condition value. This implicit conversion from non-boolean values to boolean is based on some rules. These rules define for example that all numbers, except 0 and NaN, are converted to true.

The term truthy describes all values that will be converted to true.
The term falsy describes all values that will be converted to false.

const value = 'test' // can be any other type

if (value) { // value gets converted to true or false
    // value is truthy
} else {
    // value is falsy
}

Truthy values

true ⸺ The boolean value true stays true.
{} new Date() [] ⸺ Non primitive values get converted to true (including 'empty' ones).
42 -42 3.14 -3.14 ⸺ All non-zero numbers get converted to true.
Infinity -Infinity ⸺ Infinity values get converted to true.
"dog" 'cat'' ⸺ Non-empty strings get converted to true.

Falsy values

false ⸺ The boolean value false stays false.
null ⸺ Null gets converted to false.
undefined ⸺ Undefined gets converted to false.
0 ⸺ The number 0 gets converted to false.
NaN ⸺ The value for 'Not a Number' gets converted to false.
'' "" ⸺ Empty strings get converted to false.

Ugly exception ☹

document.all gets converted to false, even if it's a collection filled with items. This clearly violates the rule "Non primitive values get converted to true (including 'empty' ones)". Most developers, however, can just forget about this exception. Read this stackoverflow question if you want to know more.

Null, undefined, 0

We know now that null, undefined and 0 share the common property of being falsy. This means, all three get implicitly converted to false if needed.

const a = undefined;

if (a) { // value of a get`s converted to false
    // this gets never executed
}
const b = null;

if (b) { // value of b get`s converted to false
    // this gets never executed
}
const c = 0;

if (c) { // value of c get`s converted to false
    // this gets never executed
}

Common pitfalls

Numeric fields that can be null

Let's say we want to communicate a status. The status will have a numeric errorCode from 0 to 5 if something goes wrong, otherwise the errorCode will be set to null.

const status = {
    'errorCode': null, // This will be set to a number in case of an error
};

status.errorCode = doSomething()
// Let's naively check if an error has occurred
if (status.errorCode) { // errorCode gets implicitly converted to a boolean
    console.log('Error!')
} else {
    console.log('Ok')
}

You might not immediately notice the possible flaw in this code, but there is a big one! In case of any positive or negative number, this code will work.

But what happens when status.errorCode is 0?

Based on the rules from above, 0 gets implicitly converted to false. Our example would ignore the error and print "Ok".

Equals comparison

You may now be tempted to fix the issue by adding an explicit null check like shown in the following example.

const status = {
    'errorCode': null,
};

status.errorCode = doSomething()

if (status.errorCode == null) { // our naive fix
    console.log('Ok')
} else {
    console.log('Error!')
}

This leads us right into the next common pitfall.

Most TypeScript developers know that they should always compare values with === instead of ==. But why is it better to use the triple equals operator? The last example shows why you should use in most cases the === operator.

The double equals operator == tries to be smart in a very unintuitive way. If you use it to compare two values of a different type, it will try to convert them to a common type. The common type is usually boolean, therefore it converts both sides to a boolean, according to above conversion rules. They only get compared after the conversion!

status.errorCode = 0
if (status.errorCode == null) // our naive fix

A status.error of 0 will get converted to false. null always gets converted to false. The comparison status.errorCode == null would become

if (false == false) // our naive fix

meaning

 if (true) // our naive fix

For our use case, it would mean to print "Ok" when an error with the errorCode=0 arises.

const status = {
    'errorCode': null,
};

status.errorCode = 0

if (status.errorCode == null) {
    console.log('Ok') // It will print 'Ok', despite the errorCode of 0
} else {
    console.log('Error!')
}

Since this is obviously not what we intended to do, what should we do?

The correct fix would have been to use the === operator, since it doesn't cause any fancy conversions.

const status = {
    'errorCode': null,
};

status.errorCode = 0

if (status.errorCode === null) { // No 'smart' conversions take place.
    console.log('Ok') 
} else {
    console.log('Error!') // It will correctly print 'Error!'
}

Some more examples:

undefined == nullfalse == falsetrue
undefined === nullfalse

undefined == 0false == falsetrue
undefined === 0false

undefined == nullfalse == falsetrue
undefined === nullfalse

'' == 0false == falsetrue
'' === 0false

Read this stackoverflow question for more details and examples regarding double equals == vs triple equals ===.

tl;dr

0 is just the number zero null, undefined and 0 get implicitly converted to false if needed.
=== is your friend!
== can not be trusted!