JavaScript Optional Chaining: Uses and Traps

Use ?. for genuinely optional paths
Optional chaining accesses a property, element, or method only when the value immediately to its left is not null or undefined. Otherwise, that chain returns undefined instead of throwing at that access.
const city = user?.profile?.address?.city;
const firstItem = order?.items?.[0];
const result = plugin?.initialize?.();
The three forms are property access (object?.property), computed access (object?.[key]), and optional call (functionValue?.()). MDN's operator reference documents all three.
Use the operator when missing data is part of the contract: an account may have no avatar, an analytics callback may be absent, or an API field may be optional. If a value is required, silently producing undefined can move a useful error farther from its cause.
Pair it with ??, not an automatic ||
Optional chaining returns undefined for a missing path. Nullish coalescing supplies a fallback only for null or undefined:
const displayName = user?.profile?.displayName ?? "Anonymous";
const itemCount = cart?.items?.length ?? 0;
By contrast, || replaces every falsy value, including 0, "", and false:
const savedVolume = settings?.volume ?? 50; // keeps 0
const wrongVolume = settings?.volume || 50; // replaces 0
Choose based on the data contract. An empty display name may deserve a fallback, while a zero volume is likely deliberate. Syntax cannot decide product meaning; it has enough responsibilities already.
Add ?. at every nullable step
This expression protects only user:
const city = user?.profile.address.city;
If user exists but profile is null, accessing .address throws. Write user?.profile?.address?.city only when each step may be absent. If profile must exist whenever user exists, validate that invariant instead of decorating the entire path with question marks.
Optional chaining short-circuits one continuous chain. Grouping can end that protection:
const profile = user?.profile; // profile may be undefined
const name = profile.name; // then this can throw
const alsoUnsafe = (user?.profile).name;
Store and check the intermediate value, or keep the intended access in one chain.
Optional call checks absence, not callability
This is safe when onSave is null or undefined:
hooks.onSave?.(record);
It still throws a TypeError when hooks.onSave exists but contains a string, object, or other non-function value. Validate unknown plugin or API data before calling:
if (typeof hooks.onSave === "function") {
hooks.onSave(record);
}
Optional call is excellent for a callback whose type is already guaranteed but whose presence is optional. It is not runtime schema validation in a small hat.
Our cancelable-fetch tutorial uses activeController?.abort() because the variable intentionally starts as null and later holds an AbortController.
Know what it cannot protect
An undeclared root identifier still throws:
missingVariable?.name; // ReferenceError
Declare the variable first, even if its value is undefined. Optional chaining also cannot appear on the left side of a normal assignment:
user?.profile = {}; // SyntaxError
And optional chaining cannot be used as the constructor target of new. Use an explicit condition when a constructor itself is optional. These restrictions keep assignment and construction behavior unambiguous.
Side effects inside a skipped computed access do not run:
let index = 0;
const value = maybeList?.[index++];
// index stays 0 when maybeList is null or undefined
Avoid relying on that subtlety for important state changes. An explicit branch is easier to read and test.
Do not hide malformed required data
Suppose an order response must contain a customer ID. This code conceals a contract failure:
const customerId = response?.order?.customer?.id;
Validate at the boundary instead:
function requireCustomerId(response) {
const id = response?.order?.customer?.id;
if (typeof id !== "string" || id.length === 0) {
throw new Error("Response is missing a customer ID");
}
return id;
}
Optional chaining can help inspect the path inside validation; it should not replace the validation. Browse language tools for similar operator boundaries and Web APIs for applying them to external data.
The practical rule is short: use ?. when absence is expected and has a defined meaning. Raise, validate, or branch when absence signals a bug. Fewer crashes are good; fewer clues are not the same achievement.