Problem Solved

JavaScript · optional chaining

Home › Courses › JavaScript › Problems › Optional chaining
🟨 Concept · 3 min read

Optional chaining (?.).
Safe access. Not validation.

?. is the safe-navigation operator. If the value on the left is null or undefined, the expression short-circuits to undefined instead of throwing. That's it — a safety check for one specific case. It's not a substitute for validation. If a property should exist and doesn't, ?. hides the bug. This page covers the syntax, the method-call gotcha, and the rule for when to use it.

Short answer: Use user?.address?.city to safely access nested properties that might be missing. Use obj?.method?.() to safely call methods that might not exist. Combine with ?? for defaults: user?.name ?? 'Anonymous'. Use ?. only when the absence is expected and legitimate — not to hide bugs. ?. doesn't work with delete or assignment. It's supported in all modern browsers and Node 14+.

✅ Before and after

The same access, with and without ?..

optional-chaining.js
const user = {
name: 'Alice',
address: {
city: 'Lahore'
}
};

const missingUser = null;

// ❌ Without ?. — throws
console.log(missingUser.address.city);
// TypeError: Cannot read property 'address' of null

// ✅ With ?. — returns undefined
console.log(missingUser?.address?.city);
// undefined

// ✅ With ?. and ?? — returns a default
console.log(missingUser?.address?.city ?? 'Unknown');
// "Unknown"

// ✅ Works on the existing user too
console.log(user?.address?.city);
// "Lahore"
How ?. works:

missingUser?.address
→ missingUser is null → short-circuit
→ returns undefined
→ the rest of the chain is skipped

missingUser?.address?.city
→ same: undefined at the first ?.
→ city is never accessed

user?.address?.city
→ user is not null → continue
→ address is not null → continue
→ city is accessed → "Lahore"

The chain short-circuits at the first nullish value.
Everything after that is skipped.

✅ No TypeError.
✅ No try/catch needed.
✅ The expression evaluates to undefined.

⚠️ This is not validation.
If user SHOULD exist, ?. hides the bug.
Use it only when the value is legitimately optional.

✅ Method calls — the double ?.

The gotcha that trips people up.

method-chaining.js
const obj = {
method() {
return 'called';
}
};

const empty = {};

// ✅ obj exists, method exists
console.log(obj?.method?.()); // "called"

// ✅ empty exists, method doesn't
console.log(empty?.method?.()); // undefined

// ❌ empty exists, method doesn't — but only one ?.
console.log(empty?.method());
// TypeError: empty.method is not a function

// The difference:
// empty?.method → checks if empty exists
// empty?.method() → calls method — if method
// doesn't exist, throws
// empty?.method?.() → checks if method exists too
The rule for method calls:

obj?.method()
→ checks: does obj exist?
→ if yes, calls obj.method()
→ if obj.method is undefined, throws

obj?.method?.()
→ checks: does obj exist?
→ if yes, checks: does obj.method exist?
→ if yes, calls it
→ otherwise returns undefined

Common mistake:

❌ const result = api?.getUser();
→ if api exists but getUser doesn't,
this throws.

✅ const result = api?.getUser?.();
→ safe for both missing api and missing method.

For array access:
arr?.[0] → safe if arr is nullish
arr?.[0]?.name → safe for both

For chained calls:
user?.getAddress?.()?.city
→ safe at every level.

✅ ?. with ?? — the default pattern

Safe access with a fallback.

nullish-default.js
const user = {
profile: {
name: 'Alice',
bio: null
}
};

// Safe access + default
const name = user?.profile?.name ?? 'Anonymous';
// "Alice"

const bio = user?.profile?.bio ?? 'No bio yet';
// "No bio yet" (bio is null)

const website = user?.profile?.website ?? 'No website';
// "No website" (website is undefined)

// Without ?. or ?? — throws or returns undefined:
// user.profile.website → undefined
// user.missing.profile → TypeError

// The pattern:
// ?. for access
// ?? for default
// ?? only triggers on null/undefined,
// not on 0, '', false
Why ?? and not ||:

const count = user?.stats?.count ?? 0;
→ if count is 0, returns 0 (correct)
→ if count is null/undefined, returns 0

const count = user?.stats?.count || 0;
→ if count is 0, returns 0 (wrong — 0 is falsy)
→ returns 0 anyway, but for the wrong reason
→ the logic is wrong even though the output
happens to match in this case

More visible case:

const score = user?.score ?? 100;
→ if score is 0, returns 0 (correct)
→ if score is null, returns 100

const score = user?.score || 100;
→ if score is 0, returns 100 (WRONG)
→ 0 is a valid score, but || treats it as
"no value"

Rule:
?. for safe access
?? for defaults
|| only when you want to replace any falsy value
(rare)

Don't use ?. to hide bugs. If a property should exist — an API response that always includes user, a config object that always has apiUrl — using ?. silently returns undefined instead of throwing. The bug surfaces later, in a different place, with a less helpful error message. ?. is for values that are genuinely optional: a middle name, a bio field, an address that the user may not have entered. For values that must exist, let the error throw — or validate explicitly. The error is a feature, not a bug. It tells you the data contract is broken.

⚠️ Common mistakes

The optional-chaining mistakes that cost real time.

1. obj?.method() without the second ?.

Throws if method doesn't exist. Fix: obj?.method?.().

2. Using ?. everywhere

Hides bugs. Fix: only when the value is genuinely optional.

3. delete obj?.prop

Syntax error. Fix: if (obj) delete obj.prop.

4. obj?.prop = value

Syntax error. Fix: explicit check, or ??= for assignment.

5. Confusing ?. with ??

Different operators. Fix: ?. for access, ?? for defaults.

6. ?. on a value that's guaranteed

Redundant. Fix: direct access. The error is useful if it happens.

7. Not knowing browser support

ES2020 — older browsers need transpilation. Fix: check target or use a build step.

8. ?. inside a chain with side effects

obj?.a()?.b() — if a has side effects, they may not run. Fix: explicit conditionals for complex logic.

❓ Frequently asked

What is optional chaining in JavaScript?

Optional chaining (?.) is a syntax for safely accessing nested properties. If any intermediate value is null or undefined, the expression short-circuits and returns undefined instead of throwing. Example: user?.address?.city returns undefined if user or address is missing. It works on properties, methods, and array indices. Added in ES2020.

What is the difference between ?. and ?.[]?

?. is for property access — user?.name. ?.[] is for indexed access — arr?.[0] or obj?.[key]. They work the same way: if the left side is null or undefined, the expression returns undefined instead of throwing. Use ?. for named properties, ?.[] for dynamic keys and array indices.

Does optional chaining work on method calls?

Yes, but with a caveat. obj?.method() calls method if obj exists, otherwise returns undefined. But this doesn't check if method exists — if obj exists but method is undefined, calling obj.method() still throws. To safely call a method that might not exist, use obj?.method?.(). The double optional chaining checks both obj and method.

What is the difference between ?. and ??

They solve different problems. ?. is for safe property access. ?? is for default values. They're often used together: const name = user?.profile?.name ?? 'Anonymous'. The ?. safely accesses the nested property, and the ?? provides a fallback if the result is null or undefined.

Should I use optional chaining everywhere?

No. Optional chaining hides bugs. If a property should exist and doesn't, ?. silently returns undefined instead of throwing — which means the bug surfaces later, in a different place. Use ?. when the absence of the value is expected and legitimate. Don't use ?. to paper over missing data that should be there.

Does optional chaining work with delete?

No. delete obj?.prop is a syntax error. Optional chaining works for access, not for mutation. If you need to safely delete a property, check first: if (obj) delete obj.prop. The same applies to assignment — obj?.prop = 5 is a syntax error. Optional chaining is read-only.

🔗 Continue learning

🗺️ JavaScript learning path
The full roadmap from variables to async — every concept in order

Next in this series:

📦 Spread vs rest
Same syntax, opposite purpose — when to use each
← All JavaScript problems
Problem solved · JavaScript