CodingNic

Arrays, Objects & Iteration

Nested Objects and Safe Access

Arrays, Objects & Iteration 20 min read

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.

javascript
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.

javascript
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.

javascript
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.

javascript
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.

javascript
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.

javascript
const order = { id: 1 };
console.log(order.customer?.address?.city);
// undefined

Optional chaining works on array indexes too, with ?.[ ].

javascript
const order = { id: 1 };
console.log(order.items?.[0]);
// undefined

It also works before calling a function that might not exist, using ?.().

javascript
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.

javascript
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.

javascript
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

  1. Create a nested object company with a name and an address object containing city and country. Log company.address.country.
  2. Using the same company object, try reading company.address.zip.code directly and read the TypeError Node gives you.
  3. Rewrite that same read using ?. so it returns undefined instead of throwing.
  4. Add ?? "Not provided" to the end of your optional chain and log the result.
  5. Given const settings = { theme: "" }, compare settings.theme || "light" and settings.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 undefined or null throws a TypeError, which happens often with nested data that isn’t guaranteed to be complete.
  • ?. short-circuits to undefined instead of throwing when a link in the chain is missing.
  • ?? supplies a fallback only for null or undefined, unlike ||, which also replaces valid falsy values like 0 and "".

Next lesson: looping over arrays and objects, and which loop to reach for.