Optional Chaining
Reaching into a nested object is one of the most common sources of runtime crashes in JavaScript. The moment one link in the chain is null or undefined, accessing a property on it throws TypeError: Cannot read properties of undefined. Optional chaining (?.), introduced in ES2020, lets you walk through deep or uncertain structures and short-circuit safely to undefined instead of blowing up. It is the cleanest way to handle data that might not be fully populated — API responses, optional config, partial form state.
The problem it solves
Before optional chaining you had to guard every level of access manually, which produced noisy, repetitive code:
const user = { profile: { name: "Ada" } };
// Without optional chaining
const city =
user && user.address && user.address.city
? user.address.city
: undefined;
console.log(city);
Output:
undefined
If you skip those guards and user.address is missing, the access throws:
const user = { profile: { name: "Ada" } };
console.log(user.address.city); // TypeError: Cannot read properties of undefined (reading 'city')
Accessing properties safely
The ?. operator checks the value on its left. If that value is null or undefined, the whole expression short-circuits and evaluates to undefined. Otherwise it proceeds with the property access as normal.
const user = { profile: { name: "Ada" } };
console.log(user.profile?.name); // "Ada"
console.log(user.address?.city); // undefined (no throw)
console.log(user.address?.city?.toUpperCase()); // undefined
Output:
Ada
undefined
undefined
Note that ?. only protects against null and undefined — not against missing values further down a non-nullish object. {}.profile?.name is fine, but ({ profile: 5 }).profile.name still throws because 5 is not nullish.
Calling methods that may not exist
Use ?.() to call a function only if it is actually defined. This is ideal for optional callbacks or feature-detecting APIs.
function notify(onDone) {
// Call the callback only if one was passed
onDone?.("finished");
}
notify(); // no error, nothing happens
notify((msg) => console.log(msg)); // logs the message
Output:
finished
Be careful:
obj.method?.()guards againstmethodbeingnull/undefined, but ifmethodexists and is not a function (e.g. a string), the call still throwsTypeError: ... is not a function.
Safe array and dynamic access
The bracket form ?.[] applies the same short-circuit to indexed or computed access. It is handy for arrays that might be absent and for dynamic keys.
const data = { tags: ["js", "ts"] };
console.log(data.tags?.[0]); // "js"
console.log(data.tags?.[99]); // undefined (in-bounds check not needed)
console.log(data.missing?.[0]); // undefined (no throw)
const key = "tags";
console.log(data?.[key]?.length); // 2
Output:
js
undefined
undefined
2
Short-circuiting behavior
When ?. short-circuits, evaluation stops immediately — the rest of the chain, including function calls and bracket lookups, is skipped entirely. This matters when later parts of the expression have side effects.
let called = false;
const getIndex = () => {
called = true;
return 0;
};
const obj = null;
const result = obj?.list[getIndex()];
console.log(result); // undefined
console.log(called); // false — getIndex was never invoked
Output:
undefined
false
Combining with nullish coalescing for defaults
?. resolves a missing path to undefined, which pairs naturally with the nullish coalescing operator ?? to supply a fallback. Unlike ||, ?? only substitutes for null/undefined, so legitimate falsy values like 0 or "" are preserved.
const settings = { theme: { mode: "dark" }, fontSize: 0 };
const mode = settings.theme?.mode ?? "light";
const lang = settings.locale?.language ?? "en";
const size = settings.fontSize ?? 14;
console.log(mode, lang, size);
Output:
dark en 0
Here fontSize of 0 is kept because ?? ignores falsiness — using || 14 would have wrongly replaced it.
Operator reference
| Syntax | Use for | Returns when left side is nullish |
|---|---|---|
a?.b | Property access | undefined |
a?.[expr] | Computed / array access | undefined |
a?.() | Function / method call | undefined |
a ?? b | Default value | b |
Optional chaining cannot be used on the left side of an assignment.
obj?.prop = 1is a syntax error —?.is for reading, not writing.
Best Practices
- Reach for
?.when a value is genuinely optional; do not sprinkle it everywhere, as it can mask real bugs by silently swallowingundefined. - Pair
?.with??to turn a missing path into a sensible default in one expression. - Prefer
??over||for defaults so that valid0,"", andfalsevalues survive. - Use
?.()for optional callbacks and feature detection instead oftypeof fn === "function"checks. - Remember
?.guards only the link directly to its left — it is not a blanket “make this whole expression safe” tool. - Keep chains readable; if you need
a?.b?.c?.d?.e, consider validating or destructuring the data shape earlier instead.