Nested Objects and Safe Access
Objectives
By the end of this chapter, you should be able to:
- Read and update values inside objects that contain other objects or arrays
- Explain why reading a missing nested property throws an error
- Use optional chaining (
?.) to safely read a property that might not exist - Use nullish coalescing (
??) to supply a fallback value
💡 Why this matters: Real data is rarely flat. A user profile has an address, an order has a customer, an API response nests object inside object. This lesson covers how to work with that nesting without your code crashing the moment a field is missing.
Nested Objects
An object’s value can be another object. This is called nesting, and it lets you model data with real structure instead of cramming everything into one flat level.
const user = {
name: "Jordan Reyes",
address: {
city: "Nairobi",
zip: "00100",
},
};
console.log(user.address.city);
// Nairobi
Each . steps one level deeper. user.address gets the inner object, and .city reads a property off that inner object.
Objects can also nest arrays, and arrays can hold objects.
const user = {
name: "Maya",
hobbies: ["reading", "cycling"],
};
console.log(user.hobbies[0]);
// reading
const team = [
{ name: "Erin", role: "designer" },
{ name: "Priya", role: "engineer" },
];
console.log(team[1].role);
// engineer
Updating a nested value works the same way, just chain further before assigning.
const user = {
name: "Jordan Reyes",
address: { city: "Nairobi", zip: "00100" },
};
user.address.city = "Mombasa";
console.log(user.address.city);
// Mombasa
The Problem: Missing Nested Properties
Nesting works fine as long as every level actually exists. The trouble starts when one doesn’t.
const order = {
id: 1,
customer: {
name: "Jordan Reyes",
},
};
console.log(order.shipping.address);
// TypeError: Cannot read properties of undefined (reading 'address')
order has no shipping property at all, so order.shipping is undefined. The code then tries to read .address off of undefined, and JavaScript throws, because undefined has no properties to read. This is one of the most common runtime errors in JavaScript, and it gets worse the deeper your data is nested.
The Fix: Optional Chaining (?.)
Optional chaining (?.) checks whether the thing on its left is null or undefined before continuing. If it is, the whole expression short-circuits to undefined instead of throwing.
const order = {
id: 1,
customer: {
name: "Jordan Reyes",
},
};
console.log(order.shipping?.address);
// undefined
No crash. Just ?. in place of . at the one link in the chain that might not exist. You can use it at multiple links if several levels are uncertain.
const order = { id: 1 };
console.log(order.customer?.address?.city);
// undefined
Optional chaining works on array indexes too, with ?.[ ].
const order = { id: 1 };
console.log(order.items?.[0]);
// undefined
It also works before calling a function that might not exist, using ?.().
const logger = {};
logger.warn?.("This won't run");
console.log("still going");
// still going
Only use ?. where a value might genuinely be missing. If you know order.customer always exists, plain .customer is clearer.
Nullish Coalescing (??)
Optional chaining gets you a safe undefined instead of a crash, but undefined often isn’t a useful value to display or work with. Nullish coalescing (??) supplies a fallback when the value on its left is null or undefined.
const order = { id: 1 };
const city = order.customer?.address?.city ?? "Unknown";
console.log(city);
// Unknown
That’s the pattern you’ll use constantly: ?. to walk safely through a chain that might break, then ?? to turn a resulting undefined into something meaningful.
?? is easy to confuse with ||, but they behave differently. || falls back on any falsy value, including 0, "", and false. ?? only falls back on null or undefined.
const settings = { volume: 0 };
console.log(settings.volume || 10);
// 10
console.log(settings.volume ?? 10);
// 0
A volume of 0 is a real, intentional value, not a missing one. || incorrectly replaces it with the fallback; ?? correctly leaves it alone. When you’re supplying a default for a possibly-missing value, ?? is almost always what you want.
Try It
- Create a nested object
companywith anameand anaddressobject containingcityandcountry. Logcompany.address.country. - Using the same
companyobject, try readingcompany.address.zip.codedirectly and read theTypeErrorNode gives you. - Rewrite that same read using
?.so it returnsundefinedinstead of throwing. - Add
?? "Not provided"to the end of your optional chain and log the result. - Given
const settings = { theme: "" }, comparesettings.theme || "light"andsettings.theme ?? "light". Explain out loud why they differ.
Recap
- Objects can nest other objects and arrays; chain
.(or[]) to reach deeper values. - Reading a property off
undefinedornullthrows aTypeError, which happens often with nested data that isn’t guaranteed to be complete. ?.short-circuits toundefinedinstead of throwing when a link in the chain is missing.??supplies a fallback only fornullorundefined, unlike||, which also replaces valid falsy values like0and"".
Next lesson: looping over arrays and objects, and which loop to reach for.