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
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:
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— returnsbwhenaisnull,undefined,0,"",false, orNaN.a ?? b— returnsbonly whenaisnullorundefined.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.
// 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.
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.
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
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
function configure(options = {}) {
options.retries ??= 3; // only fills in when missing
options.label ??= "task"; // empty string passes through
return options;
}