JS Optional Chaining (?.) Syntax Complete Guide

Master JavaScript optional chaining (?.) to safely access deeply nested object properties, call methods, and index arrays without runtime errors.

JavaScriptbeginner
10 min read

Accessing deeply nested properties in JavaScript objects is one of the most common sources of runtime errors. A single undefined in the chain causes the entire expression to throw a TypeError. Optional chaining (?.) solves this by short-circuiting to undefined when it hits a nullish value, eliminating the need for verbose manual checks.

Introduced in ES2020, optional chaining has become one of the most widely used modern JavaScript features. This guide covers every form of the operator with practical examples you will use daily.

The Problem Optional Chaining Solves

Consider an API response where some fields may or may not exist:

javascriptjavascript
const user = {
  name: "Alex",
  address: {
    street: "123 Main St",
    city: "Portland"
  }
};
 
const anotherUser = {
  name: "Jordan"
  // no address property at all
};
 
// This works fine
console.log(user.address.city); // "Portland"
 
// This crashes
console.log(anotherUser.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')

Before optional chaining, you had to write defensive checks:

javascriptjavascript
// Old approach - verbose and hard to read
const city = anotherUser && anotherUser.address && anotherUser.address.city;
 
// Or with ternary
const city2 = anotherUser.address ? anotherUser.address.city : undefined;

Both approaches become unwieldy as nesting increases. Optional chaining replaces all of this with a single operator.

Basic Property Access with ?.

The ?. operator checks if the left side is null or undefined. If it is, the entire expression returns undefined instead of throwing an error:

javascriptjavascript
const user = {
  name: "Jordan",
  profile: {
    bio: "Full-stack developer",
    social: {
      twitter: "@jordan_dev"
    }
  }
};
 
// Safe nested access
console.log(user.profile?.bio);           // "Full-stack developer"
console.log(user.profile?.social?.twitter); // "@jordan_dev"
console.log(user.settings?.theme);         // undefined (no crash)
console.log(user.settings?.theme?.color);  // undefined (no crash)

The operator short-circuits the entire chain. Once any link in the chain evaluates to null or undefined, evaluation stops and returns undefined immediately.

Optional Chaining with Method Calls

You can use ?.() to call a method that might not exist. This is especially useful when working with objects that may or may not implement certain interfaces:

javascriptjavascript
const logger = {
  info(msg) { console.log(`[INFO] ${msg}`); },
  warn(msg) { console.log(`[WARN] ${msg}`); }
};
 
// info exists, so it runs
logger.info?.("Application started");     // [INFO] Application started
 
// debug does NOT exist, returns undefined silently
logger.debug?.("Verbose output");         // undefined (no crash)
 
// Without optional chaining this would throw
// logger.debug("Verbose output"); // TypeError: logger.debug is not a function

Calling Methods on Potentially Null Objects

javascriptjavascript
const users = [
  { name: "Maya", greet() { return `Hi, I'm ${this.name}`; } },
  { name: "Alex" }  // no greet method
];
 
users.forEach(user => {
  const greeting = user.greet?.();
  console.log(greeting ?? "No greeting available");
});
// Hi, I'm Maya
// No greeting available

Optional Chaining with Bracket Notation

Use ?.[] to safely access properties using bracket notation, which is essential for dynamic keys or keys with special characters:

javascriptjavascript
const translations = {
  en: { greeting: "Hello", farewell: "Goodbye" },
  es: { greeting: "Hola", farewell: "Adiรณs" }
};
 
const lang = "fr";
 
// Dynamic key that might not exist
console.log(translations[lang]?.greeting);  // undefined
console.log(translations["en"]?.greeting);  // "Hello"
 
// Useful with variables
function getTranslation(locale, key) {
  return translations[locale]?.[key] ?? "Translation missing";
}
 
console.log(getTranslation("es", "greeting")); // "Hola"
console.log(getTranslation("fr", "greeting")); // "Translation missing"

Optional Chaining with Arrays

You can safely access array elements that might not exist:

javascriptjavascript
const data = {
  users: [
    { name: "Alice", scores: [95, 88, 92] },
    { name: "Bob" }  // no scores
  ]
};
 
// Safe array access
console.log(data.users?.[0]?.name);       // "Alice"
console.log(data.users?.[0]?.scores?.[1]); // 88
console.log(data.users?.[1]?.scores?.[0]); // undefined (no crash)
console.log(data.users?.[5]?.name);       // undefined (index out of bounds)

Combining with Nullish Coalescing (??)

Optional chaining pairs naturally with the nullish coalescing operator (??) to provide default values:

javascriptjavascript
const config = {
  display: {
    theme: "dark"
    // no fontSize property
  }
};
 
// Optional chaining + nullish coalescing
const theme = config.display?.theme ?? "light";
const fontSize = config.display?.fontSize ?? 16;
const sidebar = config.layout?.sidebar ?? true;
 
console.log(theme);    // "dark" (found)
console.log(fontSize); // 16 (default)
console.log(sidebar);  // true (default)

This combination is strictly better than using || for defaults because ?? only triggers on null or undefined, not on 0, "", or false:

javascriptjavascript
const settings = { volume: 0, name: "" };
 
// || treats 0 and "" as falsy - WRONG defaults
console.log(settings.volume || 50);  // 50 (bug! 0 is valid)
console.log(settings.name || "Anonymous"); // "Anonymous" (bug!)
 
// ?? only triggers on null/undefined - CORRECT
console.log(settings.volume ?? 50);  // 0 (correct!)
console.log(settings.name ?? "Anonymous"); // "" (correct!)

All Three Forms Summarized

FormSyntaxUse Case
Property accessobj?.propAccess a property on a possibly nullish object
Bracket accessobj?.[expr]Dynamic property access with a possibly nullish object
Method callobj?.method()Call a method that might not exist

When NOT to Use Optional Chaining

Optional chaining is powerful, but overusing it can mask real bugs:

javascriptjavascript
// BAD - overuse hides bugs
const name = response?.data?.user?.profile?.name?.first;
// If 'first' is missing, is that a bug or expected?
 
// BETTER - chain only at the uncertainty boundary
const user = response?.data?.user;
if (user) {
  // Inside here, profile.name.first should exist
  // If it doesn't, a crash here reveals a data contract violation
  const firstName = user.profile.name.first;
}

Guidelines for When to Use ?.

ScenarioUse ?. ?Reasoning
API response dataYesExternal data is inherently unreliable
Optional object propertiesYesProperty may or may not be set by design
DOM elementsYesquerySelector might return null
Required function paramsNoMissing required data should throw immediately
Internal state you controlRarelyMasking bugs in your own code is dangerous
After explicit null checksNoAlready validated, ?. adds noise

Real-World Example: Safe API Response Parser

Here is a practical pattern for parsing API responses where the shape is not guaranteed:

javascriptjavascript
function parseUserResponse(response) {
  const user = response?.data?.user;
 
  return {
    id: user?.id ?? null,
    name: user?.name ?? "Unknown User",
    email: user?.email ?? null,
    avatar: user?.profile?.avatar?.url ?? "/default-avatar.png",
    role: user?.permissions?.role ?? "viewer",
    lastLogin: user?.activity?.lastLogin
      ? new Date(user.activity.lastLogin)
      : null,
    tags: user?.metadata?.tags ?? []
  };
}
 
// Works with complete data
const full = parseUserResponse({
  data: {
    user: {
      id: 1,
      name: "Maya",
      email: "maya@example.com",
      profile: { avatar: { url: "/avatars/maya.jpg" } },
      permissions: { role: "admin" },
      activity: { lastLogin: "2026-03-01" },
      metadata: { tags: ["developer", "lead"] }
    }
  }
});
 
// Works with partial data
const partial = parseUserResponse({ data: { user: { id: 2, name: "Alex" } } });
 
// Works with empty response
const empty = parseUserResponse({});
console.log(empty.name); // "Unknown User"
console.log(empty.avatar); // "/default-avatar.png"

Common Mistakes to Avoid

Using ?. on the Left Side of Assignment

Optional chaining is a read-only operation. You cannot use it in assignments:

javascriptjavascript
const user = {};
 
// SyntaxError - cannot use ?. for assignment
// user?.name = "Alex";
 
// Correct - check first, then assign
if (user) {
  user.name = "Alex";
}

Confusing ?. with the || Operator

javascriptjavascript
const data = { count: 0, message: "" };
 
// These are NOT equivalent
data.count?.toString();  // "0" (count exists, just equals 0)
(data.count || 10).toString(); // "10" (0 is falsy, so falls through)

Chaining After a ?. that Returned undefined

Any further property access on undefined would normally throw, but ?. handles this:

javascriptjavascript
// Each ?. independently checks for null/undefined
const val = obj?.a?.b?.c;
// Equivalent to:
// obj == null ? undefined : obj.a == null ? undefined : obj.a.b == null ? undefined : obj.a.b.c

Best Practices

  1. Use ?. at the uncertainty boundary, not on every property access. Know which parts of your data might be missing.
  2. Pair with ?? for defaults instead of using || which falsely triggers on 0, "", and false.
  3. Do not use ?. to silence bugs in data you control. If a property must exist, let it throw so you find the problem early.
  4. Use ?.() for optional callbacks in configuration objects, event handlers, and plugin systems.
  5. Keep chains short. If you need more than three ?. operators in a single expression, extract the intermediate value and add a proper null check.
Rune AI

Rune AI

Key Insights

  • Use ?. to safely access uncertain data: it returns undefined instead of throwing when a link in the chain is nullish
  • Pair with ?? for clean defaults: the combination obj?.prop ?? fallback is the modern pattern for safe data access with defaults
  • Apply at the uncertainty boundary only: overusing ?. on every property access masks real bugs in your code
  • Works for properties, brackets, and methods: ?.prop, ?.[key], and ?.() cover all access patterns
  • Supported everywhere modern JS runs: ES2020+ with full browser and Node.js support, plus transpiler options for legacy targets
RunePowered by Rune AI

Frequently Asked Questions

Does optional chaining work with delete?

Yes. You can use `delete obj?.prop` safely. If `obj` is `null` or `undefined`, the expression evaluates to `true` (like deleting a non-existent property) without throwing. This is useful when cleaning up optional configuration: `delete config?.debug?.verbose`.

What is the difference between ?. and the logical AND (&&) pattern?

Both short-circuit on nullish values, but `?.` only checks for `null` or `undefined`, while `&&` short-circuits on any [falsy value](/tutorials/programming-languages/javascript/what-are-truthy-and-falsy-values-in-javascript) (`0`, `""`, `false`, `NaN`). This means `obj?.prop` is safer for data that might contain valid falsy values. Also, `?.` is more readable for deeply nested access.

Can I use optional chaining in template literals?

Yes. It works naturally inside template literal expressions: `` `Hello, ${user?.name ?? "Guest"}` ``. The `?.` resolves first, then `??` provides the default, and the template literal interpolates the result.

Is optional chaining supported in all browsers?

Optional chaining is supported in all modern browsers (Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+) and Node.js 14+. For older environments, Babel and TypeScript both transpile it to compatible code. Check your project's browser targets before relying on it without a build step.

Does optional chaining affect performance?

The performance impact is negligible. The runtime check (`=== null || === undefined`) is extremely fast. In fact, optional chaining can be faster than manual `&&` chains because the engine can optimize the well-known `?.` pattern more effectively. Never avoid `?.` for performance reasons.

Conclusion

Optional chaining (?.) eliminates an entire category of TypeError crashes caused by accessing properties on null or undefined. Its three forms (property access ?.prop, bracket access ?.[], and method call ?.()) cover every situation where data might be missing, from API responses to optional configuration objects.