Optional Chaining & Nullish Coalescing
Optional Chaining & Nullish Coalescing
These two operators, introduced in ES2020, solve some of the most common sources of crashes in JavaScript code. Optional chaining prevents Cannot read property of undefined errors, and nullish coalescing provides a safe fallback without the quirks of ||.
Optional Chaining (?.)
Without optional chaining, accessing nested properties on potentially null values crashes your app:
const user = null;
user.name; // TypeError: Cannot read properties of null
user?.name; // undefined — safe, no crash
// Deep nesting
const order = {
customer: {
address: null
}
};
order.customer.address.city; // TypeError: Cannot read properties of null
order?.customer?.address?.city; // undefined — safe
Property Access
// Object properties
user?.name
user?.profile?.avatar?.url
// Array elements
const items = null;
items?.[0] // undefined (not items[0] which crashes)
items?.[0]?.name // also safe
// Dynamic property access
const key = "name";
user?.[key] // same as user?.name
Method Calls
// Optional method calls
const element = document.getElementById("missing");
element?.addEventListener("click", handler); // no error if null
element?.classList?.add("active");
// Function might be undefined
config.onSuccess?.("done"); // calls it only if it's a function
callback?.(); // call if it exists
// Chaining optional calls
const result = obj?.method?.()?.property;
With Nullish Coalescing
The combo of ?. and ?? is extremely common:
// ?. returns undefined when chain fails
// ?? provides a fallback for null/undefined
const city = user?.address?.city ?? "Unknown";
const port = config?.server?.port ?? 3000;
const theme = user?.preferences?.theme ?? "light";
// vs || which has the falsy-value problem
const volume = settings?.audio?.volume ?? 0; // correct: 0 stays 0
const volume = settings?.audio?.volume || 0; // wrong: 0 becomes 0 (but 0 IS a valid volume)
Practical Examples
// API response handling
async function getUsername(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// Data might have unexpected structure
return data?.user?.profile?.displayName ?? data?.user?.name ?? "Anonymous";
}
// Event handler
function handleClick(event) {
const value = event?.target?.value?.trim() ?? "";
if (!value) return;
processInput(value);
}
// Configuration merging
function getConfig(userConfig) {
return {
timeout: userConfig?.network?.timeout ?? 5000,
retries: userConfig?.network?.retries ?? 3,
debug: userConfig?.logging?.debug ?? false,
};
}
// React — accessing props safely
function UserCard({ user }) {
const avatar = user?.profile?.avatarUrl ?? "/default-avatar.png";
const displayName = user?.displayName ?? user?.email ?? "Unknown User";
return `<img src="${avatar}" alt="${displayName}" />`;
}
Nullish Coalescing Assignment (??=)
// Set a value only if it's currently null or undefined
let config = {};
config.timeout ??= 5000; // config.timeout = config.timeout ?? 5000
config.retries ??= 3;
// Compared to:
config.timeout = config.timeout ?? 5000; // same effect
Logical Assignment Operators (All Three)
// ??= — assign if null/undefined
a ??= b; // a = a ?? b
// ||= — assign if falsy (0, "", false, null, undefined)
a ||= b; // a = a || b
// &&= — assign if truthy
a &&= b; // a = a && b
// Examples
let user = { name: "Alice", preferences: null };
user.preferences ??= {}; // set preferences if null
user.preferences.theme ??= "light"; // set theme if not set
let count = 0;
count ||= 1; // count becomes 1 (0 is falsy)
// Be careful: this also triggers on 0, "" etc. Use ??= for numbers!
let cached = getFromCache();
cached &&= transform(cached); // only transform if it exists
When NOT to Use Optional Chaining
Optional chaining can mask bugs. Sometimes you want the crash because it means something unexpected happened:
// If user is always supposed to exist at this point,
// crashing early is better than silently returning undefined
function getUsername(user) {
return user.name; // intentional — user must exist
}
// vs a public API where the caller might pass null
function formatName(user) {
return user?.name ?? "Anonymous"; // defensive
}
Use ?. at system boundaries (API responses, user input, optional config) and trust your internal data structures to be what you expect.
Next lesson: Array Methods Mastery — map, filter, reduce, and every array method you need.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises