JavaScriptOptional Chaining (?.)

Optional Chaining

Reading a property of null or undefined throws a TypeError. For years this meant writing chains of a && a.b && a.b.c to traverse data safely. Optional chaining (?.), added in ES2020, replaces that ritual with a single operator that returns undefined the moment any link in the chain is null or undefined.

The basic operator

JS
const user = { profile: { name: "Ada" } };
const empty = {};

console.log(user.profile?.name);    // 'Ada'
console.log(empty.profile?.name);   // undefined — no error
console.log(empty.missing?.deep?.path);   // undefined

The expression a?.b reads as: "if a is null or undefined, the whole expression is undefined; otherwise, evaluate a.b."

Three forms — property, call, index

?. has three shapes that match the three kinds of access JavaScript supports.

  • obj?.prop — read a property only if obj is not nullish.

  • obj?.() — call a function only if it exists. Works for "maybe-defined" callbacks.

  • obj?.[key] — bracket access only if obj is not nullish. Use for dynamic keys.

JS
function notify(callback, channel) {
  // Call callback only if it was provided
  callback?.("hello");

  // Bracket access — useful when key is computed
  const url = channel?.["webhook-url"];
  return url;
}

console.log(notify(msg => console.log("got:", msg)));
got: hello
undefined
Short-circuiting

Once a ?. decides the chain is undefined, everything to the right is skipped — including function calls. That is what makes long chains safe.

JS
const data = null;

// None of the .toUpperCase() or [0] is evaluated
const result = data?.user?.name?.toUpperCase()?.[0];
console.log(result);   // undefined
Side effects are skipped
If `f?.()` short-circuits, `f` was never called — no logs, no fetches, no mutations. That is usually what you want, but be aware of it.
?. vs &&

The classic safe-access pattern a && a.b && a.b.c short-circuits on any falsy value: null, undefined, 0, "", NaN, false. Optional chaining only short-circuits on null and undefined. That precision matters.

JS
const obj = { count: 0 };

// Old style — bails out because count is 0 (falsy)
console.log(obj.count && obj.count.toString());   // 0 (and never calls toString)

// New style — count is defined, so call proceeds
console.log(obj.count?.toString());               // '0'
What ?. returns when the chain breaks

The result of a broken chain is always undefined — never null, even if the missing link was null. That uniformity makes downstream checks easier.

JS
const data = { user: null };
console.log(data.user?.name);   // undefined — not null

// Combine with default via nullish coalescing
const name = data.user?.name ?? "guest";
console.log(name);   // 'guest'
Where ?. does not help
  • It does not assign. a?.b = 1 is a syntax error — there is no "optional set".

  • It does not delete with delete a?.b in all engines historically — modern engines allow it, but the meaning is "skip if a is nullish".

  • It is not a check for missing keys. ({ a: undefined }).a?.b short-circuits even though the key exists.

  • Long chains can hide bugs — if you ?. everywhere, real type errors get silently turned into undefined.

A realistic example

JS
async function loadComments(id) {
  const res = await fetch(`/api/posts/${id}`);
  const json = await res.json();

  // Server may return { post: null } for an unknown id
  const comments = json?.post?.comments ?? [];

  return comments.map(c => ({
    text: c.body,
    author: c.author?.name ?? "anonymous",
    avatar: c.author?.avatar?.url,
  }));
}

The function never throws on missing data — it produces sensible defaults at each level.

When to reach for it
  • External or user-supplied data where shape is uncertain (JSON, query params, DOM).

  • Optional callbacks passed into a function.

  • Accessing properties through the DOM: document.querySelector("h1")?.textContent.

  • Combining with ?? for chain-with-default in one line: user?.settings?.theme ?? "light".

Don't use it as a shrug
If a value should always exist, let the error happen — that is how bugs surface early. Use `?.` for genuinely optional paths, not as a blanket "make errors go away" tool.
One sentence
`?.` walks the chain and quietly returns `undefined` the first time it meets `null` or `undefined` — saving you from `TypeError: cannot read property of undefined`.