Structured data: arrays and objects · Lesson 25 of 77 · concept

Shapes inside shapes

About 60 minutes.

By the end of this lesson you can

  • Read a value several levels down a nested structure, one step at a time.
  • Combine array indexes and object keys in a single path.
  • Explain why a missing middle step throws while a missing last step is only undefined.
  • Choose between an array of objects and an object of objects for a given job.
  • Build a nested literal that models a small piece of real data.

Real data is not flat

Why it matters

So far every array has held single values and every object has held single values. Real data almost never looks like that. An order has a customer; a customer has an address; an order has lines, and each line is itself an item with a quantity and a price.

You do not need anything new for this. An array element can be an object. An object's value can be an array. That is the whole feature — the pieces you already know simply nest inside each other, as deep as the data requires.

What is new is the reading. A path like `order.customer.address.city` looks like one operation and is actually four, and knowing that is the difference between debugging it in thirty seconds and staring at it for an hour.

A path is several steps, not one

Explanation

Read `order.customer.address.city` strictly left to right. Start with `order`, which is an object. Take its `customer` property, which gives you another object. Take that object's `address` property, which gives another object. Take that object's `city` property, which is finally a string.

Each step produces a value, and the next step is applied to that value. Every intermediate result has to be something that can have properties, or the next step cannot happen.

This is why printing the steps is such an effective debugging technique. If `order.customer.address.city` is not what you expect, print `order.customer`, then `order.customer.address`, and the first one that is `undefined` tells you exactly where the data stopped matching your assumption.

Arrays of objects: the shape you will see most

Explanation

The single most common structure in real programs is an array whose elements are objects — a list of users, a list of order lines, a list of search results. Each element has the same keys, and the array supplies the order.

Reading one of them alternates the two operations you already know: square brackets when the current value is an array, a dot when it is an object. `order.items[0].name` is a dot, then an index, then a dot.

Do not be put off by the length of these expressions. There is no new rule in `order.items[0].name` — it is just the same two moves taken in turn.

A missing middle step throws; a missing last step does not

A common mistake

You already know that reading a property that does not exist gives `undefined`. `order.customer.email` on a customer with no email is simply `undefined`, and nothing goes wrong.

Now try to step through that `undefined`. `order.delivery.address`, where the order has no `delivery` property, asks `undefined` for a property — and that is a TypeError. The program stops there, with a message saying it cannot read a property of `undefined`.

So the rule is not check everything, it is check anything you intend to step through. The last step in a path is safe; every step before it is a place the walk can fall over. This is exactly what Lesson 7's `?.` operator is for, but do the checking by hand first — the operator is much easier to use well once you know what it is replacing.

Arrays are objects too

A mental model

`typeof []` is `'object'`, and so is `typeof {}` and `typeof null`. `typeof` is not a good tool for telling these apart, which surprises people who reach for it first.

`Array.isArray(value)` is the reliable way to ask whether something is an array. It is `true` for arrays and `false` for plain objects, `null`, strings and everything else.

The reason arrays report as objects is that an array really is an object with numeric keys and a `length` that maintains itself. You do not need that fact to use arrays, but it explains why `list[-1]` quietly gives `undefined` rather than complaining: it is just another key that has never been set.

A list or a lookup?

Why it matters

When you design a nested shape, the first question is whether each level is a list or a lookup. A list keeps order and allows duplicates: `[{ id: 'a' }, { id: 'b' }]`. A lookup finds one thing instantly by name: `{ a: { ... }, b: { ... } }`.

Lists are the right default when order matters or when you will present the whole thing in sequence. Lookups are right when you mostly need to answer what is the record for this id, because finding it costs nothing rather than requiring a search.

You do not have to choose forever — Module 9 is about converting between the two and deciding deliberately. For now, notice which one your data wants, and be able to say why.

Recap

Recap

Nesting adds no new syntax: arrays hold objects, objects hold arrays, and you read them with a dot or an index depending on what the previous step gave you. Reading a missing last step is `undefined`; stepping through a missing middle step is a TypeError. `Array.isArray` is how you tell an array from a plain object, because `typeof` says `object` for both.

Worked examples

Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.

Walking a path one step at a time

The same value reached in one expression and in four, so you can see that a long path is nothing more than repeated single steps.

1const order = {2  id: "A-1001",3  customer: { name: "Ada", address: { city: "London" } },4};5 6console.log(order.customer.address.city);7 8const customer = order.customer;9const address = customer.address;10console.log(address.city);11console.log(Object.keys(order));
  • line 6

    One line, four steps: order, then customer, then address, then city.

  • line 8

    The same walk taken slowly. Printing each of these is how you find where a path breaks.

  • line 11

    The nested objects are values like any other, so the top level has just two keys.

What it prints

LondonLondon["id","customer"]

An array of objects

Reading individual records out of the commonest shape in real code, alternating an index and a dot.

1const items = [2  { name: "Notebook", quantity: 2, price: 3.5 },3  { name: "Pen", quantity: 1, price: 1.25 },4];5 6console.log(items.length);7console.log(items[0].name);8console.log(items[1].price);9console.log(items[0].quantity * items[0].price);10console.log(Array.isArray(items), Array.isArray(items[0]));
  • line 6

    The array knows how many records it holds — two.

  • line 7

    Index first (the array), then a dot (the object it gave back).

  • line 9

    Both properties come from the same element, read separately.

  • line 10

    typeof would say 'object' for both. Array.isArray tells them apart properly.

What it prints

2Notebook1.257true false

The line that throws

Two harmless reads followed by one that stops the program, so the difference between a missing last step and a missing middle step is concrete rather than abstract.

1const order = { id: 7, customer: { name: "Ada" } };2 3console.log(order.customer.email);4console.log(order.delivery);5console.log(order.delivery.address);
  • line 3

    Missing LAST step. The customer has no email, so this is undefined — harmless.

  • line 4

    Also fine. There is no delivery property, so this is undefined.

  • line 5

    Missing MIDDLE step. This asks undefined for a property, which throws a TypeError and stops the program here.

This one does not run hereThe last line throws on purpose. Run it in the pad if you want to see the message — the example exists for the error, not for printed output.

Check yourself

Not scored, not stored. Getting one wrong is the useful part.

Given `const order = { customer: { name: 'Ada' } };` — which of these throws a TypeError?

What does this print?

const team = [{ name: "Ada" }, { name: "Bo" }];console.log(team[1].name);console.log(team.length);console.log(team[0].role);

What is `typeof [1, 2, 3]`?

What does this print?

const post = { title: "Hello", tags: ["js", "beginners"] };console.log(post.tags[1]);console.log(post.tags.length);console.log(post.author);

Write it yourself

Exercise · intro · 9 tests

Build the shape, then read from it

Finish the `post` object so that it holds a `title` string, an `author` object with `name` and `email` properties, and a `tags` array of at least two strings. The actual words are up to you — the tests check the shape, not the content. Then write `authorName()`, which returns the author's name, and `tagAt(index)`, which returns the tag at that position or undefined when there is no such tag.

What your code must do

post.author must be an object, not a string, and post.tags must be a real array with at least two elements. tagAt must not throw for a position that does not exist.

Define post, authorName and tagAt at the top level of your code — the tests call them by name.

Runs on your device in a sandbox with no network and no access to this page.

Exercise · core · 8 tests

Read an order safely

The `order` object below has a customer with an address, and a list of item lines. Write `customerCity()`, which returns the city; `itemNameAt(index)`, which returns the name of the item on that line or undefined when there is no such line; and `lineTotal(index)`, which returns quantity times price for that line, or 0 when there is no such line. None of them may throw.

What your code must do

itemNameAt and lineTotal are called with positions that do not exist, so they must check before stepping into a line. customerCity walks four levels down a path that is always present. lineTotal returns a number in every case, because its callers add it up.

Define order, customerCity, itemNameAt and lineTotal at the top level of your code — the tests call them by name.

Runs on your device in a sandbox with no network and no access to this page.

Worth remembering

  • Why does order.a.b throw when order.a does not exist, but order.a alone does not?

    Reading a missing property gives undefined. Stepping through that undefined asks it for a property, which is a TypeError. The last step of a path is safe; earlier steps are not.

  • How do you tell an array from a plain object?

    Array.isArray(value). typeof says 'object' for arrays, plain objects and null alike, so it cannot distinguish them.

  • List or lookup?

    An array when order matters or you present the whole thing in sequence. An object keyed by id when you mostly need to find one record by name.

Check this lesson against the source

We wrote the explanation; we did not invent the facts. This is the page that backs them.

MDN — Property accessors

Check there: That property accessors can be chained, that dot notation requires a valid identifier while bracket notation accepts any expression, and that accessing a property on null or undefined raises a TypeError.

MDN — Working with objects (JavaScript Guide)

Check there: The section on nested object initializers and reading properties from them.

MDN — Optional chaining (?.)

Check there: The shorter way to write the guard used in this lesson, covered properly in Lesson 7: the expression short circuits to undefined instead of throwing.

Prefer to read the source in a structured breakdown? Turn this doc into a breakdown

0 of 2 exercises in this lesson solved on this device

Consent version 2026-07-31.1

Cookie preferences