Problem Statement
What does obj?.prop?.nested return if obj is null?
Explanation
Optional chaining returns undefined if any part of the chain is null or undefined.
It short-circuits and stops evaluation immediately when encountering null or undefined.
Optional chaining prevents TypeError when accessing properties of null or undefined.
It works with properties, array elements, and function calls.
Optional chaining makes code safer and cleaner than multiple existence checks.
Understanding optional chaining is essential for modern defensive JavaScript.
Code Solution
SolutionRead Only
// OPTIONAL CHAINING BASICS
const obj = null;
console.log(obj?.prop?.nested); // undefined (no error)
// Without optional chaining
// console.log(obj.prop.nested); // TypeError!
// Traditional way
const value1 = obj && obj.prop && obj.prop.nested;
// PROPERTY ACCESS
const user = {
name: 'John',
address: {
city: 'NYC'
}
};
console.log(user?.name); // 'John'
console.log(user?.address?.city); // 'NYC'
console.log(user?.address?.zip); // undefined
console.log(user?.contact?.email); // undefined
const emptyUser = null;
console.log(emptyUser?.name); // undefined (not error)
// ARRAY ACCESS
const arr = [1, 2, 3];
console.log(arr?.[0]); // 1
console.log(arr?.[10]); // undefined
const nullArr = null;
console.log(nullArr?.[0]); // undefined
// FUNCTION CALLS
const obj2 = {
greet: () => 'Hello'
};
console.log(obj2.greet?.()); // 'Hello'
console.log(obj2.missing?.()); // undefined (no error)
const nullObj = null;
console.log(nullObj.method?.()); // undefined
// COMBINING WITH NULLISH COALESCING
const user2 = null;
const name = user2?.name ?? 'Guest';
console.log(name); // 'Guest'
const city = user2?.address?.city ?? 'Unknown';
console.log(city); // 'Unknown'
// PRACTICAL EXAMPLES
// API response handling
const response = await fetch('/api/user');
const data = await response.json();
const email = data?.user?.profile?.email ?? 'No email';
// Event handler
button?.addEventListener?.('click', handleClick);
// Nested object access
const config = {
server: {
host: 'localhost',
port: 3000
}
};
const port = config?.server?.port ?? 8080;
const ssl = config?.server?.ssl?.enabled ?? false;
// Array of objects
const users = [
{name: 'John', address: {city: 'NYC'}},
{name: 'Jane'},
null
];
users.forEach(user => {
console.log(user?.address?.city ?? 'No city');
});
// 'NYC', 'No city', 'No city'
// Method chaining
const result = obj
?.getData?.()
?.filter?.(x => x > 0)
?.[0];
// DELETE with optional chaining
delete user?.address?.street; // Safe
// WHEN NOT TO USE
// Don't overuse
const bad = data?.prop1?.prop2?.prop3?.prop4?.prop5;
// This might hide design issues
// Use with intention
if (user?.address) {
// We know user and address exist here
console.log(user.address.city); // No need for ?
}
// COMPARISON
// Old way - repetitive
const city1 = user && user.address && user.address.city;
// New way - clean
const city2 = user?.address?.city;
// Old way - try-catch
try {
const email1 = user.contact.email;
} catch {
const email1 = null;
}
// New way - optional chaining
const email2 = user?.contact?.email ?? null;