Nullish Coalescing
Supplying default values is one of the most common chores in JavaScript, and for years the logical OR (||) operator was the go-to tool. The problem is that || treats every falsy value as “missing” — including 0, "", false, and NaN — which quietly corrupts perfectly valid data. The nullish coalescing operator (??), introduced in ES2020, fixes this by reacting to only null and undefined. This page explains the difference, the precise semantics, and the related ??= logical assignment operator.
The problem with logical OR
|| returns its right-hand operand whenever the left-hand operand is falsy. JavaScript has several falsy values, and most of them are legitimate data you usually want to keep.
const config = { volume: 0, label: "" };
const volume = config.volume || 100; // expected 0, got 100 😬
const label = config.label || "Untitled"; // expected "", got "Untitled" 😬
console.log(volume, label);
Output:
100 Untitled
Both defaults fired even though the original values were intentional. A volume of 0 (muted) and an empty label are valid, but || discarded them.
How nullish coalescing works
The ?? operator returns the right-hand operand only when the left-hand operand is null or undefined. Every other value — including all the falsy ones — passes straight through.
const config = { volume: 0, label: "" };
const volume = config.volume ?? 100; // 0 is kept
const label = config.label ?? "Untitled"; // "" is kept
const missing = config.timeout ?? 5000; // undefined → default
console.log(volume, label, missing);
Output:
0 5000
Tip: Read
a ?? bas “a, unless a isnullorundefined, in which case b.” It is the operator you almost always want when filling in defaults for function options or API responses.
?? vs || side by side
The two operators only diverge for falsy-but-not-nullish values. This table shows exactly when each one substitutes the default:
| Left value | left || def | left ?? def |
|---|---|---|
null | def | def |
undefined | def | def |
0 | def | 0 |
"" | def | "" |
false | def | false |
NaN | def | NaN |
"hi" / 1 | "hi" / 1 | "hi" / 1 |
Use || when any falsy value should be replaced (rare, but valid). Use ?? when you specifically mean “no value was provided.”
Short-circuiting
Like || and &&, the ?? operator short-circuits: if the left side is not nullish, the right side is never evaluated. This matters when the default is expensive or has side effects.
function loadDefaults() {
console.log("computing defaults...");
return { theme: "dark" };
}
const cached = { theme: "light" };
const settings = cached ?? loadDefaults(); // loadDefaults never runs
console.log(settings.theme);
Output:
light
Combining with other operators
You cannot mix ?? directly with || or && without parentheses — JavaScript throws a SyntaxError to prevent ambiguous precedence. Wrap the grouped expression explicitly.
// SyntaxError: Unexpected token '??'
// const x = a || b ?? c;
// Correct: make intent explicit
const x = (a || b) ?? c;
const y = a || (b ?? c);
It pairs especially well with optional chaining, which yields undefined for a missing path — exactly what ?? is designed to catch.
const user = { profile: { name: "Ada" } };
const city = user.profile?.address?.city ?? "Unknown";
console.log(city);
Output:
Unknown
Nullish coalescing assignment (??=)
The ??= operator (ES2021) assigns to a variable or property only if it is currently null or undefined. It is shorthand for x = x ?? value, and it short-circuits the assignment when a value already exists.
function createConnection(options = {}) {
options.host ??= "localhost";
options.port ??= 5432;
options.retries ??= 3;
return options;
}
console.log(createConnection({ host: "db.internal", port: 0 }));
Output:
{ host: 'db.internal', port: 0, retries: 3 }
Notice port: 0 is preserved — ??= left the caller-supplied host and port untouched and only filled in the absent retries. This makes it ideal for normalizing an options object in place without clobbering caller-supplied zeros or empty strings.
Gotcha:
??=short-circuits the write when the property already holds a value, so it won’t overwrite valid data. But on a frozen object or a setter-less property, the write can throw in strict mode if the property is nullish — guard those cases before assigning.
Best practices
- Reach for
??(not||) whenever0,"", orfalseare valid values you want to keep. - Reserve
||for cases where every falsy value genuinely means “use the default.” - Combine
?.and??to safely read deep, optional data and fall back in one expression. - Use
??=to fill in missing config/options properties in place without overwriting valid falsy values. - Always add parentheses when mixing
??with||or&&— the parser requires it. - Remember
??only checksnullandundefined; it never inspects type or truthiness beyond that.