Nullish coalescing operator - explained

tscharke

Thomas Scharke

Posted on March 29, 2021

Nullish coalescing operator - explained

The Nullish coalescing operator is a new and additional JavaScript operator that has been available since June 2020 with ECMAScript 2020 (ES2020) of the programming language.

In addition to the well-known binary logical operators && (AND) and || (OR), it is the third operator non-binary and has the notation ??.

It's always used when I want to explicitly check whether the value of a variable is available to use or, if the value is not available, to continue working with another value.

Here is the "classic" for me: Once with an if block, then in a "simplified" notation with the OR operator and last but not least in the notation with the new Nullish coalescing operator.

// Long version
let secondValue = "DEFAULT_VALUE";
if (firstValue !== null && firstValue !== undefined && firstValue !== "") {
  secondValue = firstValue;
}

// Shorthand with OR-Operator
secondValue = firstValue || "DEFAULT_VALUE";

// With Nullish-Operator
secondValue = firstValue ?? "DEFAULT_VALUE";
Enter fullscreen mode Exit fullscreen mode

The first simplification, with the OR operator, works in most cases, but does not cover the case of working with boolean values.

But let's go through it step by step and see why the variants with the OR-operator work and then switch to the usually "better" Nullish coalescing operator.

OR-Operator

The binary logical operator (Binary Logical Operator) || (OR) is defined as follows:

{expression left side} || {expression right side}

I.e. if the expression on the left side delivers the value false the expression on the right side is interpreted, otherwise the expression on the left side is interpreted.

For our "simplification" from above...

let secondValue = firstValue || "DEFAULT_VALUE";
Enter fullscreen mode Exit fullscreen mode

It means that if the variable firstValue returns the value true, this value is returned (and in this case assigned to the variable secondValue). However, if the variable firstValue returns false, the value of the right side is assigned to the variable secondValue - in my case the value DEFAULT_VALUE.

Step by step

Let's go through my example above step by step and see what I mean by...

The first simplification, with the OR operator, works in most cases, but does not cover the case of working with boolean values.

and how the Nullish coalescing operator helps us here.

To do this, I put my example into a function and then execute it:

function doSomethingAmazing(firstValue) {
  let secondValue = "DEFAULT_VALUE";
  if (firstValue !== null && firstValue !== undefined && firstValue !== "") {
    // Do somthing greate
    secondValue = firstValue;
  }

  return secondValue;
}

doSomethingAmazing(1); // 1 ✅
doSomethingAmazing(42); // 42 ✅
doSomethingAmazing(null); // DEFAULT_VALUE ✅
doSomethingAmazing(""); // DEFAULT_VALUE ✅
doSomethingAmazing(/* No value means `undefined` as value */);
// DEFAULT_VALUE ✅
doSomethingAmazing(true); // true ✅
doSomethingAmazing(false); // false ✅
Enter fullscreen mode Exit fullscreen mode

🥳 Everything works fine and the code also works with boolean values. 🥳

Reflexively, I feel like "simplifying" this code and using the possibilities of JavaScript for myself. Because I can determine that a value exists with an if (firstValue), which leads to this version of my code:

function doSomethingAmazing(firstValue) {
  let secondValue = "DEFAULT_VALUE";
  if (firstValue) {
    secondValue = firstValue;
  }

  return secondValue;
}

doSomethingAmazing(1); // 1 ✅
doSomethingAmazing(42); // 42 ✅
doSomethingAmazing(null); // DEFAULT_VALUE ✅
doSomethingAmazing(""); // DEFAULT_VALUE ✅
doSomethingAmazing(/* No value means `undefined` as value */);
// DEFAULT_VALUE ✅
doSomethingAmazing(true); // true ✅
doSomethingAmazing(false); // DEFAULT_VALUE ❌ 😮
Enter fullscreen mode Exit fullscreen mode

😮 Oops...When I pass a false to the function I get back the value DEFAULT_VALUE and not the value false as expected 🤔

I go one step further and "simplify" my code again; and this time I use the OR operator:

function doSomethingAmazing(firstValue) {
  // Executes the right operand ("DEFAULT_VALUE")
  // only if the left operand (firstValue) is falsy

  // This one-liner is also called short-circuiting operator 😃
  let secondValue = firstValue || "DEFAULT_VALUE";

  return secondValue;
}

doSomethingAmazing(1); // 1 ✅
doSomethingAmazing(42); // 42 ✅
doSomethingAmazing(null); // DEFAULT_VALUE ✅
doSomethingAmazing(""); // DEFAULT_VALUE ✅
doSomethingAmazing(/* No value means `undefined` as value */);
// DEFAULT_VALUE ✅
doSomethingAmazing(true); // true ✅
doSomethingAmazing(false); // DEFAULT_VALUE ❌ 😮
Enter fullscreen mode Exit fullscreen mode

I find the last "simplification" of my code even better. It takes away the if block and makes the code easier to read.

But both "simplifications" lead to the same unexpected result when I call the function with the value false.

What have I broken? 🤔

I haven't broken anything. I merely used, in both simplifications, the functionality of JavaScript that assumes that a value must be false (false) - that is, falsy. In the concrete case, with my if block and the OR operator, I check whether the value firstValue is false and then use the value DEFAULT_VALUE.

When is a value "falsy"

In JavaScript, a value is (false) or falsy if it is null, undefined, 0 or false.

And since this is the way it is in JavaScript, I have also changed the behaviour of my implementation with my "simplification" of the code 🤷.

Call the last two code examples with 0 (Zero):

doSomethingAmazing(0);
Enter fullscreen mode Exit fullscreen mode

Again, I want the value 0 (Zero) to be returned, but I get - logically - the value DEFAULT_VALUE 🤷

Let's get back to the actual implementation with the following expression in the if block:

firstValue !== null && firstValue !== undefined && firstValue !== "")
Enter fullscreen mode Exit fullscreen mode

From this derives my requirement that I want to check whether a value is nullish and not whether a value is falsy, as I have (unwittingly) done through my "simplifications".

What does nullish mean

With nullish it's meant that an expression must have the values null or undefined, only then it is nullish.

And exactly this is and was what I wanted to have with my first implementation and have implemented.

Can I not now "simplify" my introductory example? Do I have to manually query all nullish values in JavaScript myself?

😱😱😱 N O O O O 😱😱😱

The new one - Nullish coalescing operator (??)

This is where the new one comes into play - the third logical operator in JavaScript.

Ladies and gentlemen the Nullish coalescing operator 🚀🚀🚀, which is written in JavaScript as ?? and is defined as follows:

{expression left side} ?? {expression right side}

This operator behaves similarly to the OR operator, but with the crucial difference...

It checks if the expression on the left side is "nullish".

And not, as with the OR operator, whether the expression is false.

A few examples of the Nullish coalescing operator:

1 ?? "DEFAULT VALUE"; // Result is: 1 ✅
42 ?? "DEFAULT VALUE"; // Result is: 42 ✅
null ?? "DEFAULT VALUE"; // Result is: DEFAULT VALUE ✅
undefined ?? "DEFAULT VALUE"; // Result is: DEFAULT VALUE ✅
true ?? "DEFAULT VALUE"; // Result is: true ✅
false ?? "DEFAULT VALUE"; // Result is: false ✅
0 ?? "DEFAULT VALUE"; // Result is: 0 ✅
"" ?? "DEFAULT VALUE"; // Result is: "" ❓
Enter fullscreen mode Exit fullscreen mode

And with this knowledge, I can also "simplify" my code example again - like this...

function doSomethingAmazing(firstValue) {
  // Executes the right operand ("DEFAULT_VALUE")
  // only if the left operand (firstValue) is nullish
  let secondValue = firstValue ?? "DEFAULT_VALUE";

  return secondValue;
}

doSomethingAmazing(1); // 1 ✅
doSomethingAmazing(42); // 42 ✅
doSomethingAmazing(null); // DEFAULT_VALUE ✅
doSomethingAmazing(/* No value means `undefined` as value */);
// DEFAULT_VALUE ✅
doSomethingAmazing(true); // true ✅
doSomethingAmazing(false); // false ✅
doSomethingAmazing(""); // "" ❓
Enter fullscreen mode Exit fullscreen mode

I have one more...

In my examples with the Nullish coalescing operator you will have noticed that calling my "simplified" functions with an empty string ("") does not result in DEFAULT_VALUE being returned.

This is not relevant to the way my example works, but I don't want to hide from you why this happens.

The answer is obvious: The nullish coalescing operator (??) checks whether a value is nullish, i.e. whether it's null or undefined. And an empty string ("") is an empty string in JavaScript and thus neither null nor undefined - but falsy 🤣

Another example

Let's go one step further and work with boolean values like true and false this time. Let's say, in the context of a configuration that should give a sign of life exactly when we are online and assumes that we are (always) online (by default):

function doSomethingAmazingWithAConfiguration({ online }) {
  // We use the OR operator
  let sendKeepAlive = online || true;

  return sendKeepAlive;
}

// We say explicit that we're online
doSomethingAmazingWithAConfiguration({ online: true }); // true ✅

// We use the default-state
doSomethingAmazingWithAConfiguration({}); // true ✅

// We say explicit that we're offline ⚠️
doSomethingAmazingWithAConfiguration({ online: false }); // true ❌ 😮
Enter fullscreen mode Exit fullscreen mode

At this point in the text I have now reckoned with the false return value of the last call to the function, but it is not what I wanted.

I want the return value of the function to give me false when we're offline, i.e. when we set the key online in the passed object to false ({ online: false }).

The known problem

From what I've learned, this wrong result of my function call makes sense. Because online || true has the following values with the last call: false || true.

And if the left side of the OR operator returns false the value of the expression on the right side is used (the value of the left side is falsy) - in our case true 🤷.

The code works exactly as written, but not as expected.

Possible solutions

For my function that expects a configuration object, I could work with Destructuring and define a default value:

function doSomethingAmazingWithAConfiguration({ online } = { online: false }) {
  return online;
}
Enter fullscreen mode Exit fullscreen mode

Or, instead of a configuration object, I use a boolean and check it with the strict inequality operator (!==):

function doSomethingAmazingWithAConfiguration({ online }) {
  let sendKeepAlive = online !== false;

  return sendKeepAlive;
}
Enter fullscreen mode Exit fullscreen mode

But in this article the Nullish coalescing operator is the star 🤩 and for my configuration function also a solution:

function doSomethingAmazingWithAConfiguration({ online }) {
  // We use the Nullish coalescing operator
  let sendKeepAlive = online ?? true;

  return sendKeepAlive;
}

// We say explicit that we're online
doSomethingAmazingWithAConfiguration({ online: true }); // true ✅

// We use the default-state
doSomethingAmazingWithAConfiguration({}); // true ✅

// We say explicit that we're offline
doSomethingAmazingWithAConfiguration({ online: false }); // false ✅
Enter fullscreen mode Exit fullscreen mode

Note

💖 💪 🙅 🚩
tscharke
Thomas Scharke

Posted on March 29, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related