Optional Chaining & Nullish Coalescing
Two operators introduced in ES2020 that make working with potentially-missing values far less verbose.
Optional chaining (?.)
Safely accesses a nested property, only continuing if everything along the chain is not null/undefined — short-circuits to undefined instead of throwing.
const user = { profile: { name: "Ada" } };
console.log(user.profile.name); // "Ada"
console.log(user.address.city); // TypeError: Cannot read properties of undefined
console.log(user.address?.city); // undefined — no error, short-circuits immediately
console.log(user.profile?.name); // "Ada" — still works normally when the value exists
// Before optional chaining, this required manual guards at every level
const city = user && user.address && user.address.city;
// With optional chaining
const city2 = user?.address?.city;
-
Optional method calls
const api = { fetchData() { return "data"; } }; console.log(api.fetchData?.()); // "data" console.log(api.deleteData?.()); // undefined — deleteData doesn't exist, but no TypeError -
Optional array/bracket access
const users = [{ name: "Ada" }]; console.log(users[0]?.name); // "Ada" console.log(users[5]?.name); // undefined — no error even though index 5 doesn't exist
Nullish coalescing (??)
Returns the right-hand value only when the left-hand value is null or undefined — unlike ||, it does not treat other falsy values (0, "", false, NaN) as missing.
const settings = { volume: 0, name: "" };
console.log(settings.volume || 50); // 50 — WRONG, 0 is falsy so || overrides it
console.log(settings.volume ?? 50); // 0 — RIGHT, 0 is a valid, intentional value
console.log(settings.name || "Guest"); // "Guest" — WRONG if empty string was intentional
console.log(settings.name ?? "Guest"); // "" — RIGHT, "" wasn't nullish
console.log(settings.theme ?? "light"); // "light" — theme really is missing (undefined)
Combining both
They’re commonly used together to safely read a nested value and fall back to a default only when it’s truly missing.
function getVolume(config) {
return config?.audio?.volume ?? 50; // safe traversal + meaningful default
}
console.log(getVolume({ audio: { volume: 0 } })); // 0 — respects the explicit 0
console.log(getVolume({ audio: {} })); // 50 — volume is undefined, falls back
console.log(getVolume(undefined)); // 50 — config itself is missing, falls back
Nullish coalescing assignment (??=)
Assigns only if the current value is null/undefined — shorthand for x ?? (x = y).
const config = { retries: 0 };
config.retries ??= 3;
console.log(config.retries); // 0 — untouched, 0 isn't nullish
config.timeout ??= 5000;
console.log(config.timeout); // 5000 — was undefined, so it got assigned