Tonight I'd Like Some Nullish Coalescing Operator


type coercion

Writing code is a bit like talking to machines—and they’re usually more logical than you, and a bit short-tempered.

Machine: “Do you want dinner?”

Richard: “Tonight I’d like a fierce cheese-beef croissant.”

Machine: “I asked whether you want dinner—not what you want.”

Luckily, in JavaScript the machine is a bit more easygoing. If you answer with a dinner name, it treats that as yes and shows your dinner on screen.

  const dinnerOfRichard = 'cheese-beef croissant' // I answered “cheese-beef croissant”
  // the machine decides what to display
  const coercion = (dinnerOfPerson) => {
  // if dinnerOfPerson is empty—null, undefined, or empty string—JavaScript treats it as false
    if(dinnerOfPerson){
      console.log('Yes, you're having dinner, and it's:',dinnerOfRichard)
    } else {
      console.log('You're not having dinner')
    }
  }
  coercion(dinnerOfRichard);// screen: “Yes, you're having dinner, and it's: cheese-beef croissant”
  const dinnerOfJolin = '// Jolin didn't answer
  coercion(dinnerOfJolin);// screen: “You're not having dinner”

If you don’t answer, or you answer “no” (false, 0, empty string, null, undefined), the machine accepts those too and shows what you planned: “You’re not having dinner.” Internally it thinks, “Since you didn’t name a food, I’ll assume you’re skipping dinner.”

That’s called coalescing.

It’s also why some people say JavaScript’s type coercion is too loose—bugs can be hard to find. On the other hand, it’s a core feature: short code that matches how humans think.

Type coercion shows up all over JavaScript, for example in “and” and “or” logic.

“And” and “or”

“And” and “or” aren’t only for machines—humans use them constantly. For example:

  const isGangGoing = true; // Gang is going hiking
  const isXienGoing = true; // Xien is going hiking
  // the machine decides whether I go hiking
  const  wantHiking = (isGangGoing,isXienGoing)=>{
    if(isGangGoing && isXienGoing) {
      console.log('OK, I'll go hiking')
    } else {
      console.log('I'm not going hiking')
    }
  }
  wantHiking(isGangGoing,isXienGoing);
  // screen: “OK, I'll go hiking”

In human language: with && as the boundary, if Gang is going hiking (isGangGoing) and (&&) Xien is going hiking (isXienGoing)—both true—I go hiking; otherwise I don’t.

If you change && (and) to || (or), it becomes: if Gang is going, or (||) Xien is going—either one—I go hiking; otherwise I don’t.

Eagle
Eagle at Malabang Mountain

What I find interesting is that JavaScript’s && and || reach the same result with a process that isn’t quite human intuition. For &&, JS thinking in human words becomes:

If Gang is going (isGangGoing is true), then whether Xien goes (isXienGoing true or false) decides whether I go—if Xien goes, I go; otherwise I don’t. If Gang is not going (isGangGoing is false), then no matter whether Xien goes, I don’t go.

For || it becomes:

If Gang is going, I definitely go, no matter whether Xien goes. If Gang is not going, then whether Xien goes decides—if Xien goes, I go; otherwise I don’t.


For “and” and “or,” JS and humans differ in the process of judging, but the result is the same. JS process:

  • condition1 && condition2 means: if the left of && is true, return the right (condition2); if the left is false, return the left.
  • condition1 || condition2 means: if the left of || is true, return the left (condition1); if the left is false, return the right (condition2).

JS result:

  • condition1 && condition2 => both true → true; any false → false.
  • condition1 || condition2 => any true → true; both false → false.

With type coercion, the sides of && and || don’t have to be black-and-white true/false—they can be answers like “cheese-beef croissant.” Meaning: Richard saying no, blank, or “there is no Richard” (false, null, undefined, 0, empty string) counts as false; anything else counts as true.

The clever part: after judging true/false, it doesn’t return true or false—it returns whatever was there, e.g. the string “cheese-beef croissant.” That lets you write code that’s almost unfairly short:

  let dinnerOfJolin = `cheese-beef croissant` // Jolin wants “cheese-beef croissant”
  // the machine shows the order on screen
  const coercion = (dinnerOfPerson) => {
     console.log(dinnerOfPerson && `Tonight I'd like ${dinnerOfPerson}`)
  }
  coercion(dinnerOfJolin); // screen: “Tonight I'd like cheese-beef croissant”, because Jolin ordered
  dinnerOfJolin = ``// Jolin said nothing
  coercion(dinnerOfJolin);// screen shows nothing—not even “Tonight I'd like”

In human language: if dinnerOfJolin has something (“cheese-beef croissant”), it’s true, then return the right of && and show “Tonight I’d like cheese-beef croissant”; if empty, show nothing, because it returns the left—dinnerOfJolin.

|| works the other way:

  let dinnerOfJolin = `` // Jolin said nothing
  // the machine shows Jolin's order on screen
  const coercion = (dinnerOfJolin) => {
    console.log(dinnerOfJolin || `Jolin didn't order`)
  }
  coercion(dinnerOfJolin);// screen: “Jolin didn't order”
  dinnerOfJolin = `Tonight I'd like cheese-beef croissant`// Jolin said “Tonight I'd like cheese-beef croissant”
  coercion(dinnerOfJolin);// screen: “Tonight I'd like cheese-beef croissant”

In human language: if dinnerOfJolin is empty, it’s false, then show the right of ||—“Jolin didn’t order”; if it has something (“cheese-beef croissant”), show the left—“Tonight I’d like cheese-beef croissant.”

nullish coalescing operator

If you’ve followed this far, && and || should be clear—and ES2020’s ?? (nullish coalescing operator) is easy. In short, it replaces || in special cases.

For example: Machine: “How much are you paying for dinner?”

I wanted the screen to show “0” when I shamelessly answer 0, and “You haven’t answered yet” when I say nothing.

But JS’s || can’t do what I want:

  let payOfRichard=0 // I answered 0
  // the machine shows how much I pay
  const coercion = (payOfRichard) => {
    console.log(payOfRichard || `You haven't answered yet`)
  }
  coercion(payOfRichard)// screen: “You haven't answered yet”

That shows “You haven’t answered yet”—not what we want; we want “0”. Change || to ?? and it works. Below, when I haven’t said anything, it shows “You haven’t answered yet.”

  let payOfRichard; // I haven't said anything
  // the machine shows how much I pay
  const nco = (payOfRichard) => {
    console.log(payOfRichard ?? `You haven't answered yet`)
  }
  nco(payOfRichard) // shows “You haven't answered yet”
  payOfRichard=0 // I answered 0
  nco(payOfRichard) // shows “0”
  payOfRichard=100
  nco(payOfRichard) // shows “100”

With the nullish coalescing operator, we can again command machines in shorter code for messier cases. Three common patterns:

  1. Want nothing shown when there’s no value, and the value when there is one → use &&.
  2. Want a default message when there’s no value or the value is 0, and the value otherwise → use ||.
  3. Want a default when there’s no value, but when there is a value—whether 0 or not—show that value → use ??.

That way of thinking shows up in human life too. When I used to negotiate patent cases, for some company bosses it helped to invite their wives along—because by the time the boss calls you in, he’s already decided to file, but the person who controls the money may not have agreed yet. The machine logic we just learned, boss && wife, applies: closing the deal depends more on the wife. Helping her understand what a patent is and why to apply seems to raise the odds.