Advanced20 min read

Optional Chaining & Nullish Coalescing

Safely access deeply nested properties with optional chaining (?.) and provide defaults with nullish coalescing (??).

The Problem

Accessing nested properties on null or undefined throws a TypeError — one of the most common errors in JavaScript.

javascript
const user = { name: 'Alice' };
console.log(user.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')

Traditional solution — check every level:

javascript
if (user && user.address && user.address.city) {
  console.log(user.address.city);
}

// Or with ternary
const city = user && user.address ? user.address.city : 'Unknown';

This is verbose and gets worse with deeper nesting:

javascript
if (user && user.company && user.company.address && user.company.address.zip) {
  // finally safe to use user.company.address.zip
}

Modern JavaScript provides two elegant operators to solve this problem: optional chaining (?.) and nullish coalescing (??).

Optional Chaining (?.)

The optional chaining operator (?.) returns undefined instead of throwing an error when accessing a property on null or undefined.

Property access:

javascript
const user = { name: 'Alice' };

console.log(user.address?.city);       // undefined (no error!)
console.log(user.address?.city?.length); // undefined

Bracket notation:

javascript
const key = 'city';
console.log(user.address?.[key]); // undefined

Method calls:

javascript
const user = { name: 'Alice' };
console.log(user.greet?.());    // undefined (no error even though greet doesn't exist)

const admin = {
  name: 'Bob',
  greet() { return `Hi, I'm ${this.name}`; }
};
console.log(admin.greet?.());   // "Hi, I'm Bob"

Short-circuit behavior: When the left side is null or undefined, the entire chain stops and returns undefined. Nothing to the right is evaluated:

javascript
const user = null;
console.log(user?.address?.city); // undefined (no error)
// user is null, so .address is never accessed

Note: Optional chaining only checks for null and undefined, not other falsy values like 0, '', or false.

Nullish Coalescing (??)

The nullish coalescing operator (??) provides a default value when the left side is null or undefined.

javascript
const name = null ?? 'Anonymous';
console.log(name); // 'Anonymous'

const score = undefined ?? 0;
console.log(score); // 0

Key difference from ||:

The || operator returns the right side for any falsy value (null, undefined, 0, '', false, NaN). The ?? operator only triggers on null and undefined:

javascript
// || treats 0, '', false as "missing"
0 || 42;       // 42 (0 is falsy)
'' || 'hello'; // 'hello' ('' is falsy)
false || true; // true (false is falsy)

// ?? only treats null/undefined as "missing"
0 ?? 42;       // 0 (0 is NOT null/undefined)
'' ?? 'hello'; // '' ('' is NOT null/undefined)
false ?? true; // false (false is NOT null/undefined)

Combining with optional chaining:

javascript
const city = user?.address?.city ?? 'Unknown';
// If user, address, or city is missing → 'Unknown'

Logical assignment operators (ES2021):

javascript
let a = null;
a ??= 'default';  // a is now 'default' (was null)

let b = 0;
b ??= 42;          // b is still 0 (0 is not null/undefined)

let c = '';
c ||= 'fallback';  // c is 'fallback' ('' is falsy)

let d = 5;
d &&= d * 2;       // d is 10 (5 is truthy)

Practical Examples

Optional chaining and nullish coalescing shine in real-world scenarios.

API responses with optional fields:

javascript
// API might return partial data
const response = await fetchUser(id);

const avatar = response?.data?.user?.avatar ?? '/default-avatar.png';
const bio = response?.data?.user?.bio ?? 'No bio provided';

Configuration objects with defaults:

javascript
function createApp(config) {
  const port = config?.server?.port ?? 3000;
  const host = config?.server?.host ?? 'localhost';
  const debug = config?.debug ?? false;

  console.log(`Starting on ${host}:${port}`);
}

createApp({ server: { port: 8080 } });
// Starting on localhost:8080

createApp(undefined);
// Starting on localhost:3000

DOM element chains:

javascript
// Safely access nested DOM elements
const text = document.querySelector('.card')?.querySelector('.title')?.textContent ?? '';

Chaining multiple ?. operators:

javascript
const users = [
  { name: 'Alice', address: { city: 'Paris' } },
  { name: 'Bob' }, // no address
  null,            // null entry
];

const cities = users.map(u => u?.address?.city ?? 'Unknown');
console.log(cities); // ['Paris', 'Unknown', 'Unknown']

Optional Chaining & Nullish Coalescing in Action

html
<div id="output"></div>

<script>
  // Before: verbose null checks
  const user1 = { name: 'Alice', address: { city: 'Paris' } };
  const user2 = { name: 'Bob' };
  const user3 = null;

  // After: clean optional chaining + defaults
  const city1 = user1?.address?.city ?? 'Unknown';
  const city2 = user2?.address?.city ?? 'Unknown';
  const city3 = user3?.address?.city ?? 'Unknown';

  // ?? vs || with falsy values
  const score = 0;
  const withOr = score || 'no score';   // 'no score' (wrong!)
  const withQQ = score ?? 'no score';   // 0 (correct!)

  // Optional method call
  const greet = user1?.sayHello?.() ?? 'No greeting method';

  const output = document.getElementById('output');
  output.innerHTML = `
    <p>City 1: ${city1}</p>
    <p>City 2: ${city2}</p>
    <p>City 3: ${city3}</p>
    <p>score || default: ${withOr}</p>
    <p>score ?? default: ${withQQ}</p>
    <p>Optional method: ${greet}</p>
  `;
</script>

What does `0 ?? 42` return?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.