Optional Chaining
Safe property/call access with ?. — short-circuit rules, arrays, nullish defaults, and mistakes that hide real bugs.
- javascript
- optional-chaining
Deep property access used to need nested checks or && chains. Optional chaining (?.) stops evaluation when it hits null or undefined and yields undefined instead of throwing.
// before
const city = user && user.address && user.address.city;
// after
const city = user?.address?.city;
Forms
obj?.prop
obj?.[expr]
fn?.(args)
const users = [{ name: 'Ada' }];
users?.[0]?.name; // 'Ada'
users?.[5]?.name; // undefined
api?.getUser?.(id); // call only if getUser is non-nullish
If the left side is nullish, the rest of that chain segment is skipped. If it’s non-nullish but not a function, fn?.() still throws — ?. only guards nullish, not “wrong type.”
const x = { f: 1 };
x.f?.(); // TypeError: x.f is not a function
Short-circuit, not try/catch
let n = 0;
const v = null?.[++n];
n; // 0 — right side not evaluated
const w = obj?.compute() ?? 0; // compute not called if obj nullish
Pair with ??
const theme = settings?.theme ?? 'light';
const len = list?.length ?? 0;
?. → undefined on missing; ?? → default only for nullish (keeps 0).
Assignment is not allowed
// SyntaxError
// user?.name = 'x';
Optional chaining is for reads (and calls), not writes. Write after a guard:
if (user) user.name = 'x';
Overuse is a smell
// Hides broken contracts
const id = response?.data?.user?.id;
// If API always returns data.user, failing open to undefined delays the bug
Use ?. at real optional boundaries (partial data, optional widgets, progressive enhancement). At hard invariants, throw or validate (Zod, etc.).
delete and other operators
delete obj?.prop; // deletes if obj non-nullish; no-op-ish path if nullish (doesn’t throw)
Prefer explicit null checks for mutating operations — clarity over cleverness.
Interview answer (out loud)
“
?.short-circuits when the value before it is null or undefined and returns undefined. It works for properties, computed keys, and calls. It doesn’t make bad types safe — calling a non-function still throws. I combine it with??for defaults and avoid sprinkling it so deeply that missing data never surfaces.”
Long chains and debugging
const tax = order?.customer?.address?.country?.taxRate;
// If undefined, which link failed? Hard to tell.
When debugging intermittent data bugs, temporary explicit checks or logging at each level beat a single mega-chain. In production code, validate API payloads at the boundary so UI code can use fewer ?..
Optional call on methods
const maybeLogger = cond ? console : null;
maybeLogger?.log('hi');
obj.onComplete?.(); // fine if missing method
Useful for optional callbacks in library options objects.
Bracket access and symbols
obj?.[key];
obj?.[sym]; // works with symbol keys
Dynamic keys from user input still need allowlists — optional chaining doesn’t make property access “safe” from prototype pollution if you assign with user keys elsewhere.
Further reading
Related
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.