Advanced JS Optional Chaining Complete Guide

An advanced guide to JavaScript optional chaining. Covers the ?. operator for property access, method calls, and bracket notation, short-circuit evaluation, combining with nullish coalescing, optional chaining in arrays, deeply nested API responses, and performance considerations.

JavaScriptintermediate
11 min read

Optional chaining (?.) short-circuits a property access chain to undefined when any intermediate value is null or undefined. Introduced in ES2020, it eliminates verbose null-checking and makes deeply nested data access safe and readable.

The Problem Optional Chaining Solves

Without optional chaining, accessing nested properties requires defensive checks:

javascriptjavascript
// Without optional chaining
const city = user && user.address && user.address.city;
 
// With optional chaining
const city = user?.address?.city;

Both produce undefined if anything in the chain is nullish, but the optional chaining version is shorter and clearer.

Three Forms of Optional Chaining

FormSyntaxWhat It Guards
Property accessobj?.propobj being null/undefined
Bracket notationobj?.[expr]obj being null/undefined
Method callobj?.method()obj.method being null/undefined

Property Access

javascriptjavascript
const user = { profile: { name: "Alice" } };
 
console.log(user?.profile?.name);    // "Alice"
console.log(user?.address?.city);    // undefined (no error)
console.log(null?.profile?.name);    // undefined

Bracket Notation

javascriptjavascript
const data = { "user-name": "Alice", items: ["a", "b", "c"] };
 
console.log(data?.["user-name"]);  // "Alice"
console.log(data?.["missing"]);    // undefined
 
// With dynamic keys
const key = "items";
console.log(data?.[key]?.[0]);    // "a"

Method Calls

javascriptjavascript
const logger = {
  log: (msg) => console.log(msg),
};
 
logger?.log?.("hello");    // "hello"
logger?.warn?.("caution"); // undefined (warn does not exist, no error)

The method call form checks if the method exists before invoking it.

Short-Circuit Evaluation

When ?. encounters null or undefined, the entire rest of the chain is skipped:

javascriptjavascript
const result = null?.a.b.c.d(); // undefined — b, c, d() are never evaluated

No property access or function call after the ?. runs if the left side is nullish.

Side Effects Are Skipped

javascriptjavascript
let called = false;
const obj = null;
 
obj?.method(called = true);
console.log(called); // false — the argument expression was never evaluated

Combining With Nullish Coalescing

Optional chaining returns undefined when short-circuited. Combine with ?? to provide a default:

javascriptjavascript
const theme = user?.settings?.theme ?? "light";
// If user, settings, or theme is missing: "light"
// If theme is "" (empty string) or 0: keeps the value (not nullish)
Expressionuser = nulluser.settings.theme = "dark"user.settings.theme = ""
user?.settings?.theme ?? "light""light""dark"""
user?.settings?.theme || "light""light""dark""light"

The ?? operator only falls back for null/undefined, while || also treats "", 0, and false as falsy. See advanced JS nullish coalescing full tutorial for a deep dive.

Deeply Nested API Responses

Real-world APIs often return optional nested data:

javascriptjavascript
async function getUserCity(userId) {
  const response = await fetch(`/api/users/${userId}`);
  const data = await response.json();
 
  // Safe access through multiple optional levels
  return data?.user?.addresses?.[0]?.city ?? "Unknown";
}

Normalizing API Data

javascriptjavascript
function normalizeUser(apiResponse) {
  return {
    id:      apiResponse?.id ?? null,
    name:    apiResponse?.profile?.displayName ?? "Anonymous",
    email:   apiResponse?.contact?.email ?? null,
    avatar:  apiResponse?.profile?.images?.[0]?.url ?? "/default-avatar.png",
    role:    apiResponse?.permissions?.role ?? "viewer",
    joinedAt: apiResponse?.metadata?.createdAt ?? null,
  };
}

Optional Chaining in Loops and Callbacks

javascriptjavascript
const users = [
  { name: "Alice", address: { city: "Portland" } },
  { name: "Bob" },
  null,
  { name: "Carol", address: { city: "Seattle" } },
];
 
const cities = users
  .filter(Boolean) // remove null entries
  .map(user => user?.address?.city ?? "No city")
  .filter(city => city !== "No city");
 
console.log(cities); // ["Portland", "Seattle"]

Optional Chaining With delete

javascriptjavascript
const config = { debug: { verbose: true } };
 
delete config?.debug?.verbose; // Works — deletes the property
delete config?.missing?.prop;  // No error — short-circuits

Optional Chaining Does Not Guard Assignment

javascriptjavascript
const obj = null;
 
// This DOES throw — you cannot assign through optional chaining
// obj?.name = "Alice"; // SyntaxError
 
// To conditionally assign, use an if check
if (obj != null) {
  obj.name = "Alice";
}

Optional chaining is read-only. It works for access and delete, not for assignment.

Common Patterns

Config With Multiple Fallbacks

javascriptjavascript
function getApiUrl(config) {
  return (
    config?.api?.url ??
    config?.api?.baseUrl ??
    process.env?.API_URL ??
    "http://localhost:3000"
  );
}

Safe DOM Access

javascriptjavascript
const width = document.querySelector(".sidebar")?.getBoundingClientRect()?.width ?? 0;

Optional Callback Execution

javascriptjavascript
function processData(data, options) {
  const transformed = transform(data);
  options?.onSuccess?.(transformed); // call only if provided
  return transformed;
}
 
// Both are valid
processData(data, { onSuccess: result => console.log(result) });
processData(data, {}); // onSuccess not called, no error
Rune AI

Rune AI

Key Insights

  • Three forms exist: obj?.prop, obj?.[expr], and obj?.method() -- each guards the left-hand side against being null or undefined
  • Short-circuit skips the entire rest of the chain: Once a nullish value is encountered, nothing further in that chain evaluates
  • Combine with ?? for defaults: user?.name ?? "Anonymous" provides a fallback only for null/undefined, not for falsy values like "" or 0
  • Cannot be used for assignment: obj?.prop = value is a SyntaxError; use a conditional if check for assignment
  • Replaces verbose && chains: user?.address?.city is equivalent to user && user.address && user.address.city but more precise and readable
RunePowered by Rune AI

Frequently Asked Questions

Does optional chaining work with 0 and empty string?

Yes. Optional chaining only short-circuits for `null` and `undefined`. `0?.toString()` returns `"0"`, and `""?.length` returns `0`. These are not nullish.

Can I use optional chaining on the left side of an assignment?

No. `obj?.prop = value` is a SyntaxError. Optional chaining is for reading values, not writing.

Is there a performance cost?

Negligible for typical use. The engine checks one value against null/undefined per `?.` in the chain. Do not avoid it for performance reasons.

Does optional chaining work with private class fields?

Yes. `this?.#privateField` works inside class methods if `this` might be null (rare but valid in certain callback patterns).

Is ?. the same as the old && guard pattern?

Similar but not identical. `user?.name` short-circuits on `null`/`undefined` only. `user && user.name` also short-circuits on `0`, `""`, `false`, `NaN`. Optional chaining is more precise.

Conclusion

Optional chaining eliminates defensive null-checking boilerplate for nested property access, bracket notation, and method calls. Pair it with nullish coalescing (??) for clean fallback defaults. Together, these ES2020 operators handle the vast majority of null-safety patterns. For the nullish coalescing companion, see advanced JS nullish coalescing full tutorial. For the related logical assignment operators, see logical assignment operators in JS complete guide.