HomepageExploring JavaScript (ES2024 Edition)
You can support this book: buy it or donate
(Ad, please don’t block.)

16 The non-values undefined and null

Many programming languages have one “non-value” called null. It indicates that a variable does not currently point to an object – for example, when it hasn’t been initialized yet.

In contrast, JavaScript has two of them: undefined and null.

16.1 undefined vs. null

Both values are very similar and often used interchangeably. How they differ is therefore subtle. The language itself makes the following distinction:

Programmers may make the following distinction:

16.2 Occurrences of undefined and null

The following subsections describe where undefined and null appear in the language. We’ll encounter several mechanisms that are explained in more detail later in this book.

16.2.1 Occurrences of undefined

Uninitialized variable myVar:

let myVar;
assert.equal(myVar, undefined);

Parameter x is not provided:

function func(x) {
  return x;
}
assert.equal(func(), undefined);

Property .unknownProp is missing:

const obj = {};
assert.equal(obj.unknownProp, undefined);

If we don’t explicitly specify the result of a function via a return statement, JavaScript returns undefined for us:

function func() {}
assert.equal(func(), undefined);

16.2.2 Occurrences of null

The prototype of an object is either an object or, at the end of a chain of prototypes, null. Object.prototype does not have a prototype:

> Object.getPrototypeOf(Object.prototype)
null

If we match a regular expression (such as /a/) against a string (such as 'x'), we either get an object with matching data (if matching was successful) or null (if matching failed):

> /a/.exec('x')
null

The JSON data format does not support undefined, only null:

> JSON.stringify({a: undefined, b: null})
'{"b":null}'

16.3 Checking for undefined or null

Checking for either:

if (x === null) ···
if (x === undefined) ···

Does x have a value?

if (x !== undefined && x !== null) {
  // ···
}
if (x) { // truthy?
  // x is neither: undefined, null, false, 0, NaN, 0n, ''
}

Is x either undefined or null?

if (x === undefined || x === null) {
  // ···
}
if (!x) { // falsy?
  // x is: undefined, null, false, 0, NaN, 0n, ''
}

Truthy means “is true if coerced to boolean”. Falsy means “is false if coerced to boolean”. Both concepts are explained properly in “Falsy and truthy values” (§17.2).

16.4 The nullish coalescing operator (??) for default values [ES2020]

Sometimes we receive a value and only want to use it if it isn’t either null or undefined. Otherwise, we’d like to use a default value, as a fallback. We can do that via the nullish coalescing operator (??):

const valueToUse = receivedValue ?? defaultValue;

The following two expressions are equivalent:

a ?? b
a !== undefined && a !== null ? a : b

16.4.1 Example: counting matches

The following code shows a real-world example:

function countMatches(regex, str) {
  const matchResult = str.match(regex); // null or Array
  return (matchResult ?? []).length;
}

assert.equal(
  countMatches(/a/g, 'ababa'), 3);
assert.equal(
  countMatches(/b/g, 'ababa'), 2);
assert.equal(
  countMatches(/x/g, 'ababa'), 0);

If there are one or more matches for regex inside str, then .match() returns an Array. If there are no matches, it unfortunately returns null (and not the empty Array). We fix that via the ?? operator.

We also could have used optional chaining:

return matchResult?.length ?? 0;

16.4.2 Example: specifying a default value for a property

function getTitle(fileDesc) {
  return fileDesc.title ?? '(Untitled)';
}

const files = [
  {path: 'index.html', title: 'Home'},
  {path: 'tmp.html'},
];
assert.deepEqual(
  files.map(f => getTitle(f)),
  ['Home', '(Untitled)']);

16.4.3 Using destructuring for default values

In some cases, destructuring can also be used for default values – for example:

function getTitle(fileDesc) {
  const {title = '(Untitled)'} = fileDesc;
  return title;
}

16.4.4 Legacy approach: using logical Or (||) for default values

Before ECMAScript 2020 and the nullish coalescing operator, logical Or was used for default values. That has a downside.

|| works as expected for undefined and null:

> undefined || 'default'
'default'
> null || 'default'
'default'

But it also returns the default for all other falsy values – for example:

> false || 'default'
'default'
> 0 || 'default'
'default'
> 0n || 'default'
'default'
> '' || 'default'
'default'

Compare that to how ?? works:

> undefined ?? 'default'
'default'
> null ?? 'default'
'default'

> false ?? 'default'
false
> 0 ?? 'default'
0
> 0n ?? 'default'
0n
> '' ?? 'default'
''

16.4.5 The nullish coalescing assignment operator (??=) [ES2021]

??= is a logical assignment operator. The following two expressions are roughly equivalent:

a ??= b
a ?? (a = b)

That means that ??= is short-circuiting: The assignment is only made if a is undefined or null.

16.4.5.1 Example: using ??= to add missing properties
const books = [
  {
    isbn: '123',
  },
  {
    title: 'ECMAScript Language Specification',
    isbn: '456',
  },
];

// Add property .title where it’s missing
for (const book of books) {
  book.title ??= '(Untitled)';
}

assert.deepEqual(
  books,
  [
    {
      isbn: '123',
      title: '(Untitled)',
    },
    {
      title: 'ECMAScript Language Specification',
      isbn: '456',
    },
  ]);

16.5 undefined and null don’t have properties

undefined and null are the only two JavaScript values where we get an exception if we try to read a property. To explore this phenomenon, let’s use the following function, which reads (“gets”) property .foo and returns the result.

function getFoo(x) {
  return x.foo;
}

If we apply getFoo() to various values, we can see that it only fails for undefined and null:

> getFoo(undefined)
TypeError: Cannot read properties of undefined (reading 'foo')
> getFoo(null)
TypeError: Cannot read properties of null (reading 'foo')

> getFoo(true)
undefined
> getFoo({})
undefined

16.6 The history of undefined and null

In Java (which inspired many aspects of JavaScript), initialization values depend on the static type of a variable:

In JavaScript, each variable can hold both object values and primitive values. Therefore, if null means “not an object”, JavaScript also needs an initialization value that means “neither an object nor a primitive value”. That initialization value is undefined.