JavaScriptNullish Coalescing (??)

Nullish Coalescing

The nullish coalescing operator (??), added in ES2020, is a careful cousin of ||. It returns the right-hand side only when the left-hand side is null or undefined — not for other falsy values like 0, "" or false. That small change fixes a category of bugs that lurked in defaulting logic for years.

The operator

JS
console.log(null  ?? "default");   // 'default'
console.log(undefined ?? "default");   // 'default'
console.log(0     ?? "default");   // 0
console.log(""    ?? "default");   // ''
console.log(false ?? "default");   // false
console.log("Ada" ?? "default");   // 'Ada'

Only the first two — null and undefined — trigger the fallback. Everything else is returned as-is.

Why it exists — the 0 and "" problem

|| returns the right side whenever the left is falsy, which includes valid values like 0 and the empty string. That accidentally clobbers data:

JS
function withFallback(input) {
  return {
    bad:  input.count || 10,    // 0 becomes 10 — wrong!
    good: input.count ?? 10,    // 0 stays 0 — right
  };
}

console.log(withFallback({ count: 0 }));
console.log(withFallback({ count: undefined }));
{ bad: 10, good: 0 }
{ bad: 10, good: 10 }

Rule of thumb: if a user could legitimately supply 0 or "", use ??. If you want any missing-or-empty input to fall back, || is correct.

?? vs || side by side
  • a || b — returns b when a is null, undefined, 0, "", false, or NaN.

  • a ?? b — returns b only when a is null or undefined.

  • For boolean flags, || is usually fine. For numbers and strings, prefer ??.

  • ?? makes intent clearer: "if the value is missing, use this default".

Mixing with && or || requires parens

To make precedence mistakes impossible, the spec made it a syntax error to combine ?? with && or || without explicit parentheses.

JS
// SyntaxError:
// const x = a ?? b || c;

// Be explicit
const x = (a ?? b) || c;
const y = a ?? (b || c);
Pairs beautifully with optional chaining

?. returns undefined whenever the chain breaks. ?? then supplies a default. Together they replace a lot of defensive code.

JS
function displayName(user) {
  return user?.profile?.name ?? "guest";
}

console.log(displayName({ profile: { name: "Ada" } }));   // 'Ada'
console.log(displayName({ profile: {} }));                // 'guest'
console.log(displayName(null));                           // 'guest'
??= — nullish assignment

a ??= b assigns b to a only if a is currently null or undefined. It is the natural sibling of ?? for filling in missing properties on existing objects.

JS
const settings = { theme: "dark", count: 0 };

settings.theme    ??= "light";   // theme exists — unchanged
settings.fontSize ??= 14;        // fontSize was undefined — set
settings.count    ??= 99;        // count is 0, not nullish — unchanged

console.log(settings);
{ theme: 'dark', count: 0, fontSize: 14 }

Compare with ||= (which would have clobbered count: 0) and &&= (which assigns only when the left is truthy). The three logical-assignment operators all short-circuit — the right side is not evaluated when the assignment is skipped.

A small refactor

Before

JS
function configure(options) {
  options = options || {};
  options.retries = options.retries || 3;     // breaks if user passes 0
  options.label = options.label || "task";    // breaks if user passes ""
  return options;
}

After

JS
function configure(options = {}) {
  options.retries ??= 3;       // only fills in when missing
  options.label   ??= "task";  // empty string passes through
  return options;
}
Default parameters do part of the job
Function default parameters (`options = {}`) also trigger only on `undefined` — they are essentially `??` baked into the signature. Use them whenever you can.
Mental model
`??` means "if and only if missing". It treats `null` and `undefined` as the absence of a value and leaves every other value alone.
One sentence
Reach for `??` whenever `0`, `""`, or `false` are legitimate values that should not be replaced. Otherwise `||` is still fine.