skip to content

Optional Chaining, Nullish Coalescing, and Logical Assignment

Modern operators replace defensive boilerplate: ?. short-circuits on null or undefined, ?? falls back only on nullish values (unlike ||), and &&=/||=/??= assign conditionally. Interviewers ask because the ?? versus || distinction decides whether 0 and empty string survive.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

5

In JavaScript, what is the difference between `a ?? b` and `a || b`, and which left-hand values make the two produce different results?

level: juniorimportance: must knowfreq 82%

answer

  1. one checks falsy, the other checks absent
  2. only two values count as nullish
  3. a deliberate zero is the classic casualty
  4. false, 0, empty string, NaN diverge
  5. NaN survives one of them untouched

basics

~20 s

The ?? operator falls back only when the left side is null or undefined; || falls back on any falsy value. So 0, -0, NaN, the empty string and false are kept by ?? but replaced by ||.

solid answer

~40 s

Both are short-circuiting fallback operators, but they test different things. `a || b` evaluates `a`, converts it to a boolean, and returns `b` whenever `a` is falsy — that set is `false`, `0`, `-0`, `0n`, `""`, `NaN`, `null` and `undefined`. `a ?? b` returns `b` only when `a` is `null` or `undefined`; every other value, including `0` and `""`, is returned as-is. That is why `??` was added in ES2020: the old `value || default` idiom silently destroys legitimate zero, empty-string and `false` settings, so `volume || 50` turns a deliberate mute into 50 while `volume ?? 50` keeps the `0`. Both short-circuit, so the right operand is only evaluated when it is actually needed. Note that `??` cannot be combined with `||` or `&&` without parentheses.

code

javascript · 8 lines
javascript
const settings = { volume: 0, label: '', theme: null };

console.log(settings.volume || 50);   // 50  -> deliberate mute lost
console.log(settings.volume ?? 50);   // 0   -> mute preserved
console.log(settings.label || 'n/a'); // 'n/a'
console.log(settings.label ?? 'n/a'); // ''
console.log(settings.theme ?? 'dark');// 'dark' -> null is nullish
console.log(NaN ?? 1);                // NaN -> NaN is not nullish

go deeper

for a junior

Be able to state plainly that ?? only reacts to null and undefined while || reacts to every falsy value, and to name 0, empty string and false as the values that behave differently.

for a middle

Explain the exact falsy set versus the two-value nullish set, show the silent default bug that || causes for numeric and boolean options, and note that both operators short-circuit their right operand.

for a senior

Show judgment about where each belongs: numeric and boolean settings want ??, while free-text fields where empty means missing may legitimately want || or an explicit emptiness check. Be ready to justify a migration rather than a blanket replace.

for a principal

Own the API-design angle: decide whether absent options are represented by undefined, null, or a sentinel, and whether defaults are resolved at the boundary once or re-derived at every call site. That choice, not the operator, is what keeps defaults consistent across a system.

## The distinction in one line `||` asks "is the left side falsy?". `??` asks "is the left side null or undefined?". Everything else follows from that. ## The two value sets JavaScript has exactly eight falsy values: `false`, `0`, `-0`, `0n` (the BigInt zero), `""`, `null`, `undefined` and `NaN`. Every other value — including `[]`, `{}`, `"0"` and `"false"` — is truthy. "Nullish" is a much smaller set: only `null` and `undefined`. Nothing else qualifies. `NaN` is not nullish. An empty string is not nullish. `false` is not nullish. So the values where the two operators disagree are precisely the falsy-but-not-nullish ones: `false`, `0`, `-0`, `0n`, `""` and `NaN`. ```js 0 || 50 // 50 0 ?? 50 // 0 '' || 'n/a' // 'n/a' '' ?? 'n/a' // '' false || true // true false ?? true // false NaN ?? 1 // NaN <- ?? does NOT rescue NaN null ?? 1 // 1 undefined ?? 1 // 1 ``` ## Why `||` was the historical idiom Before ES2020 there was no nullish test, so `const port = options.port || 3000` was the standard way to express "use the caller's value if they gave me one". It reads well and works fine as long as no legitimate value is falsy. The moment a legitimate value *is* falsy — a retry count of `0`, a `verbose: false` flag, an empty search string, a slider at zero — the default silently overrides the caller's explicit choice. These bugs are quiet: nothing throws, the program just behaves as if the setting was never provided. ```js function connect(opts = {}) { const retries = opts.retries || 3; // retries: 0 becomes 3 const debug = opts.debug || true; // ALWAYS true — the flag is unusable return { retries, debug }; } connect({ retries: 0, debug: false }); // { retries: 3, debug: true } ``` With `??` both values survive: ```js const retries = opts.retries ?? 3; // 0 stays 0 const debug = opts.debug ?? true; // false stays false ``` ## Short-circuit evaluation Both operators are short-circuiting: the right operand is an expression that is only evaluated if it is needed. `cached ?? computeExpensiveDefault()` does not call the function when `cached` is `0`, and `flag || warn()` does not call `warn` when `flag` is truthy. That matters whenever the right side has side effects or is costly. ## Distinguishing "absent" from "present but empty" The deeper point is that `??` lets you distinguish *missing* from *set to a falsy value*, which `||` cannot do. `undefined` typically means "the key was never provided" (a missing object property, a missing argument, an out-of-range array index). `null` usually means "explicitly set to no value". `??` treats both as absent and everything else as present. That is not always the rule you want. For a text input, an empty string usually *should* be treated as "the user typed nothing", so `||` — or better, an explicit check — is the correct semantic there. `??` is not a blanket replacement for `||`; it is the right tool when falsy-but-meaningful values exist. ## Relationship to default parameters Default parameter values and destructuring defaults use a third rule again: they trigger on `undefined` only, never on `null`. ```js function f(x = 5) { return x; } f(undefined); // 5 f(null); // null <- default does not apply const { a = 1 } = { a: null }; // a === null ``` So `??` sits between `||` (any falsy) and parameter defaults (`undefined` only). ## Syntax constraint `??` may not be mixed with `&&` or `||` in the same expression without parentheses — `a ?? b || c` is a SyntaxError, and you must write `(a ?? b) || c` or `a ?? (b || c)`. The language forces you to state which grouping you meant rather than guessing. ## What to say in an interview State the rule (nullish vs falsy), name the divergent values (`0`, `""`, `false`, `NaN`), give the classic bug (`count || 10` eating a deliberate zero), and add the judgment: `??` is the better default for numeric and boolean options, while an empty string often genuinely means "missing" and deserves an explicit check.

  • Does `??` rescue `NaN` the way it rescues `null`?
    No. `NaN` is falsy but not nullish, so `NaN ?? 1` evaluates to `NaN`. If a parse result may be `NaN` you need an explicit guard, such as `Number.isNaN(n) ? fallback : n`, because neither `??` nor a default parameter will catch it.
  • How do default parameter values compare to `??`?
    A default parameter or destructuring default applies only when the value is `undefined`; `null` is passed through unchanged. `??` treats `null` and `undefined` alike. So `function f(x = 5)` called with `null` returns `null`, whereas `x ?? 5` would return `5`.
  • Why is `a?.b ?? fallback` such a common pairing?
    Optional chaining yields `undefined` when any link in the chain is nullish, and `undefined` is exactly what `??` reacts to. So `user?.prefs?.theme ?? 'dark'` reads as "drill in if you can, otherwise use the default" without swallowing a legitimately empty or zero value found at the end of the chain.

|| is a bouncer who turns away anyone who looks unenthusiastic; ?? only turns away people who never showed up.

saying these in an interview costs you the question

  • Says ?? and || are interchangeable, just newer syntax
  • Claims ?? falls back on any falsy value including 0
  • Thinks NaN is nullish so ?? replaces it
  • Believes ?? evaluates its right operand eagerly
  • Applies ?? everywhere without checking empty-string cases

context

open as a page

In JavaScript, what exactly does the optional chaining operator `?.` short-circuit, and in which situations does an expression containing `?.` still throw?

level: middleimportance: must knowfreq 72%

basics

~20 s

Optional chaining short-circuits the whole remaining chain: if the value before ?. is null or undefined the entire expression evaluates to undefined, and later accesses, calls and their arguments are never evaluated. It only guards the link it is written on.

open as a page

In JavaScript, how do the logical assignment operators `&&=`, `||=` and `??=` differ from writing `x = x && y`, and why does that difference matter for a property with a setter?

level: middleimportance: should knowfreq 44%

basics

~20 s

Logical assignment short-circuits the assignment itself: when the test fails no write happens at all. So x ||= y writes only if x is falsy, x &&= y only if x is truthy, x ??= y only if x is null or undefined — and setters, proxy traps and frozen-object errors are skipped entirely.

open as a page

Why is `const x = a ?? b || c;` a SyntaxError in JavaScript, and how do you express either intended grouping?

level: middleimportance: should knowfreq 38%

basics

~20 s

JavaScript forbids combining ?? with || or && in one expression without parentheses, so the parser rejects it. Write (a ?? b) || c or a ?? (b || c) to state the grouping explicitly; the two mean different things.

open as a page

A JavaScript service resolves options with `const retries = cfg.retries || 3` in dozens of places, and users report that explicit `0` and `false` settings are ignored. How do you audit and fix this, and where is `??` not the right replacement?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Treat it as a semantics audit, not a find-and-replace: at each site decide which values mean absent. Numeric and boolean options should use ?? so 0 and false survive; string and array options where empty genuinely means missing need an explicit check, not ??.

open as a page