DocsHub
Objects

Optional Chaining

Learn how to safely access deeply nested object properties without errors using optional chaining in JavaScript.

Optional Chaining

When you access a property on an object, JavaScript assumes the object exists. If it does not — you get an error.

const user = null;

console.log(user.name); // ❌ TypeError: Cannot read properties of null

This is one of the most common errors in JavaScript. It happens constantly when working with API data, user input, or anything that might not always be there.

The old way to protect against this was to check at every step:

const user = null;

// Old way — verbose and painful
const city = user && user.address && user.address.city;
console.log(city); // null — no error but ugly

Optional chaining solves this cleanly. It lets you access nested properties safely — if anything in the chain is null or undefined, it stops and returns undefined instead of throwing an error.

const user = null;

console.log(user?.name);              // undefined — no error
console.log(user?.address?.city);     // undefined — no error

The ?. operator says "access this property — but only if the thing before me exists".


Basic Syntax

object?.property        // access a property
object?.method()        // call a method
object?.[expression]    // access with bracket notation
array?.[index]          // access array item

If the value before ?. is null or undefined — the whole expression short-circuits and returns undefined. If it exists — it continues normally.


Accessing Nested Properties

This is where optional chaining shines most. Deep nesting with uncertain data.

const user = {
  name: "Ali",
  address: {
    city: "Lahore",
    country: "Pakistan"
  }
};

console.log(user?.address?.city);    // Lahore
console.log(user?.contact?.phone);   // undefined — contact does not exist

Without optional chaining:

// ❌ Crashes if address is missing
console.log(user.address.city);

// ✅ Safe but ugly
const city = user && user.address && user.address.city;

With optional chaining:

// ✅ Safe and clean
const city = user?.address?.city;

With API Data

This is the most realistic use case. Data from an API is not always complete — some fields might be missing or null.

const response = {
  status: 200,
  data: {
    user: {
      name: "Ali",
      subscription: null
    }
  }
};

// subscription is null — without optional chaining this crashes
const plan = response.data.user.subscription.plan;
// ❌ TypeError: Cannot read properties of null

// With optional chaining — safe
const plan = response?.data?.user?.subscription?.plan;
console.log(plan); // undefined — no crash
// Real use — show a default when value is missing
const plan = response?.data?.user?.subscription?.plan ?? "Free";
console.log(plan); // Free

Combining optional chaining with nullish coalescing ?? is a very common pattern — access safely, fall back gracefully.


Calling Methods Safely

Optional chaining works on method calls too. If the method does not exist, it returns undefined instead of crashing.

const user = {
  name: "Ali",
  greet() {
    return `Hello, ${this.name}!`;
  }
};

console.log(user.greet?.());   // Hello, Ali!
console.log(user.goodbye?.());  // undefined — method does not exist, no error

Real use — calling a callback only if it was provided:

function fetchData(url, onSuccess, onError) {
  // simulate success
  const data = { id: 1, name: "Ali" };

  onSuccess?.(data);  // only calls if onSuccess was passed
  onError?.("Something went wrong"); // only calls if onError was passed
}

fetchData("/api/user", data => console.log(data));
// { id: 1, name: "Ali" }
// onError was not passed — no crash

Before optional chaining this required if (onSuccess) onSuccess(data). Now it is one clean line.


With Bracket Notation

Optional chaining works with bracket notation too — useful for dynamic property access.

const user = { name: "Ali", age: 22 };
const key = "name";

console.log(user?.[key]); // Ali

const missing = null;
console.log(missing?.[key]); // undefined — no error

With Arrays

const users = [
  { name: "Ali", scores: [88, 92, 76] },
  { name: "Sara" } // no scores
];

console.log(users[0]?.scores?.[0]); // 88
console.log(users[1]?.scores?.[0]); // undefined — scores does not exist

Short Circuiting

When optional chaining short-circuits — stops because something is null or undefined — the rest of the expression is not evaluated at all.

let count = 0;

const user = null;
const result = user?.incrementAndGet(count++);

console.log(result); // undefined
console.log(count);  // 0 — count++ never ran

The function call never happened because user was null. Nothing after ?. runs when it short-circuits.


What Optional Chaining Does NOT Do

Optional chaining only protects against null and undefined. It does not catch other types of errors.

const user = { name: "Ali" };

console.log(user?.name?.toUpperCase()); // ALI — fine
console.log(user?.age?.toFixed(2));     // undefined — age is undefined, stops here safely
// ❌ This still throws — name is a string, strings do not have .city
console.log(user?.name?.city?.street); // undefined — fine, just returns undefined

Also — do not overuse it. If a property should always exist, use regular dot notation. Optional chaining on everything hides bugs that should be errors.

// ❌ Overusing — hides real problems
console.log(response?.data?.users?.length);

// ✅ Only use where things are genuinely optional
console.log(response.data.users?.length); // users might be empty, but data always exists

Use optional chaining only where null or undefined is a realistic possibility — not everywhere. Overusing it can hide bugs that should be caught early.


A Real Example — User Dashboard

const users = [
  {
    name: "Ali",
    subscription: { plan: "Pro", expiresAt: "2025-12-31" },
    lastLogin: "2024-03-15"
  },
  {
    name: "Sara",
    subscription: null,
    lastLogin: "2024-03-10"
  },
  {
    name: "Zara"
    // no subscription, no lastLogin
  }
];

for (const user of users) {
  const plan = user?.subscription?.plan ?? "Free";
  const expiry = user?.subscription?.expiresAt ?? "N/A";
  const lastLogin = user?.lastLogin ?? "Never";

  console.log(`${user.name} — Plan: ${plan} | Expires: ${expiry} | Last login: ${lastLogin}`);
}

// Ali  — Plan: Pro  | Expires: 2025-12-31 | Last login: 2024-03-15
// Sara — Plan: Free | Expires: N/A        | Last login: 2024-03-10
// Zara — Plan: Free | Expires: N/A        | Last login: Never

Three users — each with different missing data. Optional chaining handles every case cleanly without a single if check.


Summary

  • Optional chaining ?. safely accesses properties on values that might be null or undefined
  • Returns undefined instead of throwing a TypeError
  • Works on properties obj?.prop, methods obj?.method(), and bracket notation obj?.[key]
  • Short-circuits — nothing after ?. runs if the value is null or undefined
  • Combine with ?? for clean fallback values — obj?.prop ?? "default"
  • Only protects against null and undefined — not other errors
  • Do not overuse it — only apply where null or undefined is genuinely expected

On this page