Advanced JS Nullish Coalescing Full Tutorial

An advanced tutorial on the JavaScript nullish coalescing operator (??). Covers the difference between ?? and ||, handling null vs undefined vs falsy values, combining with optional chaining, nested defaults, assignment patterns, and practical use cases for configuration and API response handling.

JavaScriptintermediate
10 min read

The nullish coalescing operator (??) returns its right-hand operand when the left-hand operand is null or undefined, and the left-hand value otherwise. Unlike the logical OR (||), it does not treat 0, "", false, or NaN as trigger values for the fallback. This makes it the correct tool for default values in most modern JavaScript code.

?? vs || Comparison

javascriptjavascript
const count = 0;
 
// || treats 0 as falsy — returns fallback
const a = count || 10;   // 10 (wrong if 0 is a valid count)
 
// ?? treats 0 as a real value — keeps it
const b = count ?? 10;   // 0 (correct)
Left-Side Value|| Returns?? Returns
nullRight sideRight side
undefinedRight sideRight side
0Right side0
"" (empty string)Right side""
falseRight sidefalse
NaNRight sideNaN
Truthy valueLeft sideLeft side

The rule: ?? only considers null and undefined as "missing". Everything else is a valid value.

Basic Usage

javascriptjavascript
function greet(name) {
  const displayName = name ?? "Guest";
  return `Hello, ${displayName}!`;
}
 
greet("Alice");     // "Hello, Alice!"
greet(undefined);   // "Hello, Guest!"
greet(null);        // "Hello, Guest!"
greet("");          // "Hello, !" — empty string is NOT nullish
greet(0);           // "Hello, 0!" — 0 is NOT nullish

Combining With Optional Chaining

Optional chaining (?.) and nullish coalescing (??) are designed to work together:

javascriptjavascript
const config = {
  api: {
    timeout: 0,    // zero is a valid timeout
    retries: null, // null means "use default"
  },
};
 
const timeout = config?.api?.timeout ?? 3000; // 0 (kept, not nullish)
const retries = config?.api?.retries ?? 3;    // 3 (null triggers fallback)
const baseUrl = config?.api?.baseUrl ?? "http://localhost"; // "http://localhost"

See advanced JS optional chaining complete guide for the companion operator.

Nested Defaults

Chain ?? to fall through multiple potential sources:

javascriptjavascript
function resolveTheme(user, system) {
  return (
    user?.preferences?.theme ??
    system?.defaultTheme ??
    "light"
  );
}
 
// Tries user preference first, then system default, then hardcoded fallback

Configuration Merge Example

javascriptjavascript
function createConfig(overrides = {}) {
  return {
    host:    overrides.host    ?? process.env.HOST    ?? "localhost",
    port:    overrides.port    ?? process.env.PORT    ?? 3000,
    debug:   overrides.debug   ?? false,
    logLevel: overrides.logLevel ?? "info",
  };
}
 
// port: 0 is kept (not nullish)
createConfig({ port: 0 }); // { host: "localhost", port: 0, debug: false, logLevel: "info" }

Operator Precedence

?? has lower precedence than most operators but cannot be mixed with || or && without parentheses:

javascriptjavascript
// SyntaxError — ambiguous without explicit grouping
// const x = a || b ?? c;
 
// Must use parentheses
const x = (a || b) ?? c;
const y = a || (b ?? c);

This restriction prevents subtle bugs from mixing two different "default" semantics.

Precedence Table

OperatorPrecedence (higher = binds tighter)
., ?.Very high
*, /, %High
+, -Medium-high
===, !==Medium
&&Medium-low
||Low
??Low (same level as ||)
=, ??=Very low

Nullish Coalescing Assignment (??=)

The ??= operator assigns only when the left side is null or undefined:

javascriptjavascript
const options = { timeout: 0, retries: null };
 
options.timeout ??= 5000; // 0 is not nullish — stays 0
options.retries ??= 3;    // null is nullish — becomes 3
options.debug   ??= false; // undefined is nullish — becomes false
 
console.log(options);
// { timeout: 0, retries: 3, debug: false }

See logical assignment operators in JS complete guide for all assignment operators including ||= and &&=.

Practical Patterns

Safe JSON Parsing With Default

javascriptjavascript
function parseJSON(text, fallback = null) {
  try {
    return JSON.parse(text) ?? fallback;
  } catch {
    return fallback;
  }
}
 
parseJSON('{"a":1}');         // { a: 1 }
parseJSON('null', {});        // {} — JSON.parse("null") returns null
parseJSON('invalid', []);     // [] — catch returns fallback

DOM Value Extraction

javascriptjavascript
function getInputValue(selector, defaultValue = "") {
  return document.querySelector(selector)?.value ?? defaultValue;
}

API Response Normalization

javascriptjavascript
function normalizeProduct(raw) {
  return {
    id:          raw.id,
    name:        raw.name        ?? "Untitled Product",
    description: raw.description ?? "",
    price:       raw.price       ?? 0,        // 0 from API is kept
    stock:       raw.stock       ?? null,      // null means unknown
    tags:        raw.tags        ?? [],
    rating:      raw.rating      ?? undefined, // unrated
  };
}

Lazy Initialization

javascriptjavascript
class Cache {
  #data = new Map();
 
  get(key, computeFn) {
    let value = this.#data.get(key);
    if (value === undefined) {
      value = computeFn();
      this.#data.set(key, value);
    }
    return value ?? null; // distinguish between cached undefined and null
  }
}

When to Use ?? vs ||

ScenarioUse
Want default only for null/undefined??
Want default for all falsy values||
Numeric value where 0 is valid??
String where "" is valid??
Boolean where false is valid??
Quick "any truthy value" check||

The general recommendation: default to ?? and only use || when you specifically want falsy-value fallback behavior.

Rune AI

Rune AI

Key Insights

  • ?? only triggers on null and undefined: Unlike ||, it preserves 0, "", false, and NaN as valid values
  • Cannot mix with || or && without parentheses: JavaScript enforces explicit grouping to prevent ambiguity
  • Designed as the companion to optional chaining: user?.setting ?? default is the standard null-safe-with-fallback pattern
  • ??= assigns only when nullish: obj.prop ??= value fills in missing properties without overwriting valid falsy ones
  • Right-hand side evaluation is lazy: The fallback expression is only computed when needed, making it safe for expensive operations
RunePowered by Rune AI

Frequently Asked Questions

Can I use ?? with function return values?

Yes. `someFunction() ?? defaultValue` applies the fallback if the function returns `null` or `undefined`.

Does ?? work with Symbol or BigInt?

Yes. Both Symbol and BigInt values are not nullish, so `??` returns them as-is. Only `null` and `undefined` trigger the fallback.

Is ?? supported in all browsers?

Yes, in all modern browsers (Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+). For older browsers, transpile with Babel.

Can I chain more than two ?? operators?

Yes. `a ?? b ?? c ?? d` evaluates left to right and returns the first non-nullish value, or `d` if all are nullish.

Does ?? evaluate the right side eagerly or lazily?

Lazily. The right-hand operand is only evaluated if the left-hand operand is `null` or `undefined`. This means `null ?? expensiveComputation()` only calls the function when the left side is nullish.

Conclusion

Nullish coalescing (??) is the correct default-value operator for most JavaScript code. Unlike ||, it preserves valid falsy values like 0, "", and false. Pair it with optional chaining (?.) for safe nested access with clean fallbacks. Use ??= for conditional assignment that only writes when the target is nullish. For the optional chaining companion, see advanced JS optional chaining complete guide. For the spread-based merging alternative, see JS spread vs rest operator complete tutorial.