Optional chaining and nullish coalescing

Read deeply nested data without crashing on missing parts, and supply defaults that leave 0, false and empty strings alone.

  • Beginner
  • 6 min read
  • Updated 17 Oct 2026

What you will learn

  • Read nested properties safely
  • Choose ?? over ||
  • Call methods that might not exist with ?.()
  • Use the ??= assignment operator for defaults

Before you start

Real data has gaps. A user may have no address, an API may leave out a field, a setting may never have been saved. Two operators handle these gaps cleanly: ?. reads through missing parts without throwing, and ?? supplies a default only when a value is genuinely missing. Both are supported in all current browsers and Node.js.

The problem

Reading a property of undefined or null throws:

JavaScript
const user = { name: 'Noor' };

console.log(user.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')

The old fix was a chain of checks:

JavaScript
const city = user && user.address && user.address.city;

It works, but it is noisy, and it is easy to get wrong.

Optional chaining: ?.

?. reads a property only if the value on its left is not null or undefined. If it is, the whole expression stops and gives undefined:

JavaScript
console.log(user.address?.city); // → undefined
console.log(user?.name);         // → Noor

This is called short-circuiting. Once ?. meets a missing value, nothing to its right runs. That means one ?. protects the rest of the chain:

JavaScript
const order = { customer: null };
console.log(order.customer?.address.postcode); // → undefined

address.postcode is never evaluated because customer is null.

Three forms

FormExampleUse it for
obj?.propuser.address?.cityA property that may be missing
obj?.[expr]scores?.[0], user.settings?.[key]Bracket access, including array items
fn?.()options.onSave?.(data)Calling a function that may not exist

The function form is handy for optional callbacks:

JavaScript
function save(data, options = {}) {
  console.log('Saving', data);
  options.onSuccess?.(data);
}

save({ id: 1 });
// → Saving { id: 1 }

save({ id: 2 }, { onSuccess: (d) => console.log(`Saved ${d.id}`) });
// → Saving { id: 2 }
// → Saved 2

With DOM code, ?. helps when an element might not be on every page:

JavaScript
document.querySelector('#newsletter')?.addEventListener('submit', handleSignup);

handleSignup here stands for whatever handler function you have defined.

Do not overuse it

?. hides missing data. That is right when the data is allowed to be missing, and wrong when it is not. If every user must have an id, write user.id, so a bug shows up as an error rather than a silent undefined that breaks something later. Put ?. only where a gap is expected.

You also cannot assign with it: user.address?.city = 'Leeds' is a SyntaxError.

Nullish coalescing: ??

a ?? b gives a, unless a is null or undefined, in which case it gives b. "Nullish" means exactly those two values.

JavaScript
const settings = { volume: 0, nickname: '' };

console.log(settings.volume ?? 50);     // → 0
console.log(settings.nickname ?? 'Guest'); // → (an empty string)
console.log(settings.theme ?? 'light'); // → light

Why not ||?

Before ??, people wrote defaults with ||. But || falls back on any falsy value, and 0, '', false and NaN are falsy:

JavaScript
console.log(settings.volume || 50);       // → 50 (wrong: the user chose 0)
console.log(settings.nickname || 'Guest'); // → Guest

The volume bug is a classic: the user turns the sound off, and the app turns it back up.

Value of xx || 'default'x ?? 'default'
undefined'default''default'
null'default''default'
0'default'0
'''default'''
false'default'false
'Ana''Ana''Ana'

Use ?? when 0, false or an empty string are valid values. Use || only when you really do want every falsy value replaced, for example treating an empty search box the same as no search.

Using them together

?. and ?? are designed to be combined: read safely, then fall back.

JavaScript
const response = {
  data: {
    course: { title: 'JavaScript', stats: { lessons: 0 } },
  },
};

const lessons = response.data?.course?.stats?.lessons ?? 'not counted yet';
console.log(lessons); // → 0

const author = response.data?.course?.author?.name ?? 'Unknown author';
console.log(author); // → Unknown author

This pattern is common with JSON from a server, which you will start loading in lesson 33.

Mixing with && and ||

You cannot mix ?? with && or || without brackets. JavaScript refuses to guess what you meant:

JavaScript
const a = null;
// const result = a || b ?? 'c';  SyntaxError
const result = (a || 0) ?? 'c';
console.log(result); // → 0

Default assignment: ??=

x ??= value assigns only if x is currently null or undefined. It is shorthand for x = x ?? value, except that it does not assign at all when x already has a value:

JavaScript
const prefs = { fontSize: 0 };

prefs.fontSize ??= 16;
prefs.theme ??= 'light';

console.log(prefs); // → { fontSize: 0, theme: 'light' }

It is useful for filling in missing options and for lazy caches:

JavaScript
const cache = {};

function getGreeting(language) {
  cache[language] ??= `Built greeting for ${language}`;
  return cache[language];
}

console.log(getGreeting('en')); // → Built greeting for en

Its siblings ||= and &&= assign on falsy and truthy values respectively. The same rule applies: pick ??= unless you mean "any falsy value".

Defaults in parameters versus ??

Default parameter values and destructuring defaults (lesson 23) also apply only to undefined, but not to null:

JavaScript
function greet(name = 'friend') {
  return `Hello, ${name}`;
}

console.log(greet(undefined)); // → Hello, friend
console.log(greet(null));      // → Hello, null

When null can arrive, for example from JSON where missing values are often null, use ?? inside the function instead:

JavaScript
function greetSafely(name) {
  return `Hello, ${name ?? 'friend'}`;
}

console.log(greetSafely(null)); // → Hello, friend

Summary

  • ?. reads a property, item or method only when the left side is not null or undefined, and short-circuits the rest of the chain.
  • Use ?. only where missing data is expected, so real bugs still throw.
  • ?? falls back only on null and undefined; || falls back on every falsy value, including 0 and ''.
  • Brackets are required when mixing ?? with && or ||.
  • ??= fills in missing values, and default parameters do not catch null, so use ?? when null is possible.

Further reading