HomepageExploring JavaScript (ES2025 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 sometimes 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

The nullish coalescing operator (??) lets us use a default if a value is undefined or null:

value ?? defaultValue

Examples:

> undefined ?? 'default'
'default'
> null ?? 'default'
'default'
> false ?? 'default'
false
> 0 ?? 'default'
0
> '' ?? 'default'
''
> {} ?? 'default'
{}
16.4.0.1 ?? is short-circuiting

?? is short-circuiting – the right-hand side is only evaluated if it is actually used:

let evaluated = false;

// Right-hand side is not used
123 ?? (evaluated = true);
assert.equal(evaluated, false);

// Right-hand side is used
undefined ?? (evaluated = true);
assert.equal(evaluated, true);

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 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.4 The nullish coalescing assignment operator (??=) ES2021

The nullish coalescing assignment operator (??=) assigns a default if a value is undefined or null:

value ??= defaultValue

Examples:

let value;

value = undefined;
value ??= 'DEFAULT';
assert.equal(
  value, 'DEFAULT'
);

value = 0;
value ??= 'DEFAULT';
assert.equal(
  value, 0
);
16.4.4.1 ??= is short-circuiting

The following two expressions are roughly equivalent:

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

That means that ??= is short-circuiting – the following two things only happen if a is undefined or null:

let value;

value = undefined;
value ??= console.log('evaluated');

value = 0;
value ??= console.log('NOT EVALUATED');
evaluated
16.4.4.2 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 .prop and returns the result.

function getProp(x) {
  return x.prop;
}

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

> getProp(undefined)
TypeError: Cannot read properties of undefined (reading 'prop')
> getProp(null)
TypeError: Cannot read properties of null (reading 'prop')

> getProp(true)
undefined
> getProp({})
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:

JavaScript borrowed null and uses it where objects are expected. It means “not an object”.

However, storage locations in JavaScript (variables, properties, etc.) can hold either primitive values or objects. They need an initialization value that means “neither an object nor a primitive value”. That’s why undefined was introduced.