JavaScript Optional Chaining Table
| Pattern | What it does | Field note |
|---|---|---|
obj?.prop | Read prop only if obj exists | Short-circuits to undefined - no TypeError |
obj?.method() | Call only if method exists | Skips when method absent - API responses |
arr?.[0] | Index safely | Bracket form for dynamic keys and arrays |
fn?.() | Call only if fn is callable | Callbacks that may not be passed |
a?.b.c | Only a is guarded | The ? stops the WHOLE chain at a - b.c still runs if a exists |
obj?.prop ?? fallback | Guard + default | ?? handles null AND undefined - || lets 0 and '' through |
Optional chaining is the null check that reads like the sentence you'd say: obj?.prop means 'if obj exists, read prop - otherwise undefined'. The table below is the working six: property, method, bracket and call guards, plus the two behaviors that catch everyone once - the one-link short-circuit and the ?? pairing.
Bottom line: the chain short-circuits at the FIRST nullish link. a?.b.c gives undefined when a is nullish (b.c never runs), but when a exists and b is nullish it THROWS - the guard covers exactly one link, not the whole path. Chain guards where nullishness actually appears (API responses: data?.user?.name), not everywhere.
The honest part: optional chaining removes the null checks and nullish coalescing removes the default checks - they were designed as a pair. a?.b ?? 'default' treats null and undefined as absent while keeping 0 and empty string (which || would swallow). Migrating || to ?? is the quiet bug-fix of modern codebases: form inputs with 0 stopped resetting to defaults.
How to use
- Guard where nullishness actually arrives: API responses, optional props, user-provided objects - not on values you constructed.
- Combine with ?? for defaults: data?.count ?? 0 reads 'count, or 0 when absent' - including when count is legitimately 0.
- Use fn?.() for callbacks that may not be passed - the guard tests callability, not just existence.
Frequently asked questions
Why does a?.b.c still throw when b is missing?
Because optional chaining guards ONE link: a?.b short-circuits if a is nullish, but then .c is a plain property access on the result. When a exists but b is undefined, reading c of undefined throws. The fix is chaining the guards through the whole risky path: a?.b?.c short-circuits at whichever link is nullish. Reading it as 'the guard covers the link it's attached to' predicts the behavior in every case.
How is ?. different from || when setting defaults?
|| treats ALL falsy values as absent: 0, '' and false fall through to the default. ?? (nullish coalescing) only falls through for null and undefined. The difference is data, not style: a count of 0, a volume of '' and an enabled=false are real values that || would silently replace with the default. The modern pairing is ?. for safe access and ?? for safe defaults - they were standardized together for exactly this.
When is fn?.() the right call?
When a callback is optional in the contract: hooks, event handlers, or API options where the caller may omit the function. fn?.() calls it only when it exists and is callable, returning undefined otherwise - one character-ish of code replacing if (typeof fn === 'function'). The caveat: it guards callability, not safety - the called function can still throw, so validation belongs inside the contract.
Does optional chaining work on the left side of an assignment?
No - obj?.prop = value is a SyntaxError by design: 'assign to nothing' has no meaning, and the committee chose loud failure over a silent no-op. You can still write THROUGH a guarded path into an existing container: obj?.deep[prop] = value works when obj exists. The rule: reads chain freely, writes need the container to genuinely exist - use a default object first if the target might be absent.