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.
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
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 |
|---|---|---|
null | Right side | Right side |
undefined | Right side | Right side |
0 | Right side | 0 |
"" (empty string) | Right side | "" |
false | Right side | false |
NaN | Right side | NaN |
| Truthy value | Left side | Left side |
The rule: ?? only considers null and undefined as "missing". Everything else is a valid value.
Basic Usage
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 nullishCombining With Optional Chaining
Optional chaining (?.) and nullish coalescing (??) are designed to work together:
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:
function resolveTheme(user, system) {
return (
user?.preferences?.theme ??
system?.defaultTheme ??
"light"
);
}
// Tries user preference first, then system default, then hardcoded fallbackConfiguration Merge Example
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:
// 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
| Operator | Precedence (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:
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
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 fallbackDOM Value Extraction
function getInputValue(selector, defaultValue = "") {
return document.querySelector(selector)?.value ?? defaultValue;
}API Response Normalization
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
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 ||
| Scenario | Use |
|---|---|
| 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
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 ?? defaultis the standard null-safe-with-fallback pattern - ??= assigns only when nullish:
obj.prop ??= valuefills 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
Frequently Asked Questions
Can I use ?? with function return values?
Does ?? work with Symbol or BigInt?
Is ?? supported in all browsers?
Can I chain more than two ?? operators?
Does ?? evaluate the right side eagerly or lazily?
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.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.