1. Introduction
Accessing deeply nested properties on an object that might be missing pieces used to require verbose, error-prone guard checks like obj && obj.a && obj.a.b. ES2020 introduced two operators to solve this cleanly: optional chaining (?.), which safely accesses properties/methods that might not exist, and nullish coalescing (??), which provides a default value only when the left side is null or undefined, unlike || which also triggers on any falsy value.
Cricket analogy: Before ES2020, checking a scorecard's nested data meant writing "match && match.innings && match.innings.batsman" like manually verifying each level of a scoreboard exists before reading it; now "match?.innings?.batsman" does it safely, and "runs ?? 0" only defaults when runs is truly unrecorded, not when a batsman genuinely scored 0.
2. Syntax
// Optional chaining
const city = user?.address?.city; // property access
const result = user?.getName?.(); // method call
const item = list?.[0]; // bracket/array access
// Nullish coalescing
const port = config.port ?? 8080; // fallback only for null/undefined
// Combining both
const theme = settings?.ui?.theme ?? 'light';3. Explanation
Optional chaining (?.) checks whether the value immediately to its left is null or undefined. If it is, the entire expression short-circuits and evaluates to undefined without throwing, skipping any further property accesses or calls to the right. If the value is not nullish, evaluation continues as normal. This works for property access (?.prop), computed/bracket access (?.[expr]), and function calls (?.()), and it is essential for method calls where the method itself might not exist on the object.
Cricket analogy: "player?.stats?.average" checks if player exists before reading stats, short-circuiting to undefined instead of crashing if a substitute hasn't been named yet; "player?.celebrate?.()" is essential since not every player has a signature celebration method defined.
Nullish coalescing (??) returns its right-hand operand only when the left-hand operand is null or undefined. This is an important distinction from the logical OR operator ||, which returns the right-hand side for *any* falsy value, including 0, '', false, and NaN — values that are often perfectly valid, intentional data.
Cricket analogy: "oversBowled ?? 10" only kicks in if oversBowled was never recorded, correctly leaving a genuine 0-overs opening spell untouched, whereas "oversBowled || 10" would wrongly overwrite that legitimate 0.
Gotcha — ?? vs ||: const count = 0; const displayCount = count || 10; gives 10 (wrong, since 0 is a valid count), but const displayCount = count ?? 10; correctly gives 0. Use ?? whenever falsy-but-valid values like 0, '', or false should be preserved, and reserve || for when you genuinely want to replace any falsy value.
Gotcha — optional chaining short-circuits the WHOLE chain: const x = a?.b.c.d; — if a is null/undefined, the entire expression short-circuits to undefined immediately (b, c, d are never accessed, so no TypeError occurs), but note only a?.b is optional here; if a exists but a.b is null, accessing .c next would still throw, because the ?. was only placed once. Chain ?. at every level that might be missing: a?.b?.c?.d.
4. Example
const users = [
{ name: 'Ada', profile: { visits: 0, bio: null } },
{ name: 'Grace' }
];
for (const u of users) {
const visits = u.profile?.visits ?? 'unknown';
const bio = u.profile?.bio ?? 'No bio provided';
const greet = u.sayHi?.() ?? 'no greeting method';
console.log(u.name, '| visits:', visits, '| bio:', bio, '| greet:', greet);
}5. Output
Ada | visits: 0 | bio: No bio provided | greet: no greeting method
Grace | visits: unknown | bio: No bio provided | greet: no greeting method6. Key Takeaways
- ?. safely accesses a property, index, or calls a method only if the preceding value is not null/undefined, otherwise it short-circuits to undefined.
- ?. works for property access (?.prop), bracket access (?.[expr]), and function calls (?.()).
- ?? returns the right-hand value only when the left-hand value is null or undefined.
- Unlike ||, ?? preserves valid falsy values such as 0, '', and false instead of overriding them.
- ?. must be applied at every level of a chain that might be missing, not just the first.
- Combining ?. and ?? (e.g. a?.b?.c ?? 'default') is a common, readable pattern for safe defaults.
Practice what you learned
1. What does `user?.address?.city` evaluate to if `user` is undefined?
2. Given `const count = 0;`, what does `count || 10` evaluate to, and how does that differ from `count ?? 10`?
3. What does `u.sayHi?.()` do if `u.sayHi` is undefined?
4. Given `const a = { b: null };`, what does `a?.b.c` do?
5. Which values does the nullish coalescing operator ?? treat as triggers for its fallback?
Was this page helpful?
You May Also Like
Operators in JavaScript
Survey JavaScript's arithmetic, comparison, logical, and ternary operators and how they behave with type coercion.
Objects in JavaScript
Learn how to create, access, and modify JavaScript objects, and understand why objects behave as reference types.
Destructuring in JavaScript
Learn how to unpack values from arrays and properties from objects into distinct variables using destructuring, including defaults and renaming.
Error Handling in JavaScript
How JavaScript represents runtime failures with Error objects and the built-in TypeError, RangeError, ReferenceError, and SyntaxError types.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics