Skip to main content
Adapted, not copied

Original notes for Javascript360.org β€” topic order inspired by Eloquent Javascript (4th ed.) by Marijn Haverbeke. See the full attribution note.

Chapter 4: Data Structures β€” Objects & Arrays

Estimated time: ~50 minutes Β· Chapter type: Concept

What you'll learn​

  • How to model real-world data with objects and arrays
  • The array methods you'll reach for constantly
  • Mutability β€” and why const doesn't mean "unchangeable"
  • Destructuring, rest parameters, and JSON

A running example: a sleep-tracking log​

Suppose you're building a small habit tracker that records how many hours you slept each night. A single night's entry is naturally an object β€” a bag of named properties:

const entry = {
date: '2026-09-01',
hoursSlept: 7.5,
mood: 'rested',
};

A week of entries is naturally an array β€” an ordered list:

const log = [
{ date: '2026-09-01', hoursSlept: 7.5, mood: 'rested' },
{ date: '2026-09-02', hoursSlept: 5.5, mood: 'tired' },
{ date: '2026-09-03', hoursSlept: 8, mood: 'great' },
];

Almost every real dataset you'll deal with is some combination of these two shapes: arrays of objects, objects containing arrays, and so on.

Properties​

You read and write an object's properties with either dot notation (when you know the name ahead of time) or bracket notation (when the name is dynamic, e.g. stored in a variable):

entry.mood;               // "rested"
entry['hoursSlept']; // 7.5

const key = 'mood';
entry[key]; // "rested" β€” dot notation can't do this

entry.mood = 'okay'; // writing a property
entry.tags = ['weekday']; // adding a brand-new property

Accessing a property that doesn't exist gives undefined rather than throwing an error β€” which is convenient, but means typos in property names fail silently instead of loudly. entry.hoursSleep (missing the t) simply returns undefined with no warning.

Methods​

A property whose value happens to be a function is called a method, and it's the standard way to attach behavior to a piece of data:

const logger = {
entries: [],
add(entry) {
this.entries.push(entry); // shorthand method syntax
},
average() {
const total = this.entries.reduce((sum, e) => sum + e.hoursSlept, 0);
return this.entries.length ? total / this.entries.length : 0;
},
};

logger.add({ date: '2026-09-01', hoursSlept: 7.5 });
logger.add({ date: '2026-09-02', hoursSlept: 6 });
logger.average(); // 6.75

Inside a method, this refers to the object the method was called on β€” logger, in this case. this is determined by how a function is called, not where it's defined, which is a frequent source of bugs when methods are passed around detached from their object (arrow functions, which don't have their own this, sidestep that problem β€” one reason they're popular for callbacks).

Objects, under the hood​

An object is fundamentally just a collection of key/value pairs, where keys are always strings (or Symbols β€” Chapter 6) even if you write them as numbers or bare words. Two useful built-ins for inspecting an object:

Object.keys(entry);    // ["date", "hoursSlept", "mood"]
Object.values(entry); // ["2026-09-01", 7.5, "rested"]
Object.entries(entry); // [["date", "2026-09-01"], ["hoursSlept", 7.5], ...]

Object.entries pairs nicely with for...of when you need both the key and value:

for (const [key, value] of Object.entries(entry)) {
console.log(`${key}: ${value}`);
}

Mutability​

Objects and arrays are mutable β€” you can change their contents in place β€” while primitives (numbers, strings, booleans) are not. This has a consequence that surprises a lot of newcomers: const only locks the binding, not the contents.

const scores = [10, 20];
scores.push(30); // fine β€” the array is mutated, "scores" still points at it
console.log(scores); // [10, 20, 30]

// scores = []; // TypeError β€” this reassigns the binding, which const forbids

Two objects with identical contents are still different objects, and === compares object identity, not deep equality:

{ a: 1 } === { a: 1 }; // false β€” two distinct objects, even though they "look" the same
const x = { a: 1 };
const y = x;
x === y; // true β€” y is the *same* object as x, not a copy
y.a = 2;
x.a; // 2 β€” mutating y also affects x, because they're the same object

When you want an independent copy instead of a shared reference, use structuredClone(obj) for a deep copy, or {...obj} / [...arr] (the spread operator) for a shallow one.

Array loops and essential array methods​

You already know for...of for a plain walk over an array. For anything richer β€” transforming, filtering, or searching β€” array methods are almost always clearer than a hand-written loop:

const nums = [3, 7, 1, 9, 4];

nums.length; // 5
nums.includes(9); // true
nums.indexOf(1); // 2
[...nums].sort((a, b) => a - b); // [1, 3, 4, 7, 9] β€” sort mutates in place, so copy first!
nums.push(12); // adds to the end, mutates
nums.pop(); // removes from the end, mutates, returns the removed value
nums.slice(1, 3); // [7, 1] β€” a new array, original untouched
nums.join(', '); // "3, 7, 1, 9, 4"

sort deserves a callout: by default it sorts elements as strings (so [10, 2, 1].sort() gives [1, 10, 2], not [1, 2, 10]), and it mutates the array it's called on. Always pass a comparator for numbers, and copy first ([...nums].sort(...)) if you need to preserve the original order.

We'll cover map, filter, and reduce β€” the three methods you'll use the most β€” in full in Chapter 5, since they deserve dedicated attention.

Strings and their properties​

Strings share some of the same reading operations as arrays (.length, indexing, .slice), because under the hood a string behaves like a read-only sequence of characters. But a string isn't a "real" array β€” it has no push, pop, or other mutating methods, precisely because strings are immutable (Chapter 1).

Rest parameters​

The ... syntax, when used in a function's parameter list, collects any number of remaining arguments into a real array:

function total(...amounts) {
return amounts.reduce((sum, n) => sum + n, 0);
}

total(1, 2, 3); // 6
total(10, 20, 30, 40); // 100

The same ... syntax used the other way β€” inside an array or function call β€” spreads an iterable out into individual elements, which is how you combine or copy arrays concisely:

const morning = [1, 2, 3];
const evening = [4, 5, 6];
const wholeDay = [...morning, ...evening]; // [1, 2, 3, 4, 5, 6]

The Math object​

Math is a built-in object bundling numeric constants and functions β€” not a class you instantiate, just a namespace:

Math.max(4, 9, 2);   // 9
Math.min(4, 9, 2); // 2
Math.round(4.6); // 5
Math.floor(4.6); // 4
Math.random(); // a pseudo-random number, 0 (inclusive) to 1 (exclusive)
Math.PI; // 3.14159...

Destructuring​

Destructuring pulls values out of objects or arrays directly into named bindings, which cuts down on repetitive entry.x, entry.y boilerplate:

const { date, hoursSlept } = entry;
console.log(`${date}: ${hoursSlept}h`);

const [first, second, ...rest] = [10, 20, 30, 40];
// first = 10, second = 20, rest = [30, 40]

It also works directly in function parameters, which is extremely common in real codebases:

function summarize({ date, hoursSlept }) {
return `${date}: ${hoursSlept}h of sleep`;
}
summarize(entry);

Optional property access​

Accessing a nested property when an intermediate value might not exist used to require a chain of manual checks. The ?. (optional chaining) operator does it in one step, short-circuiting to undefined instead of throwing:

const user = { profile: { name: 'Kai' } };

user.profile?.name; // "Kai"
user.settings?.theme; // undefined β€” no error, even though "settings" doesn't exist
user.settings?.theme ?? 'light'; // "light" β€” combine with ?? for a clean default

JSON: reading and writing data as text​

JSON (Javascript Object Notation) is a text format for representing objects and arrays β€” the de facto standard for sending structured data between a server and a browser, or saving it to a file. It looks almost exactly like Javascript object/array syntax, with a couple of restrictions (property names must be double-quoted strings, and functions/undefined aren't representable).

const data = { name: 'Kai', hoursSlept: [7, 6.5, 8] };

const text = JSON.stringify(data);
// '{"name":"Kai","hoursSlept":[7,6.5,8]}'

const parsedBack = JSON.parse(text);
// { name: 'Kai', hoursSlept: [7, 6.5, 8] } β€” a brand-new object, not the same reference

JSON.stringify(data, null, 2) adds indentation, which is handy for logging readable output during debugging.

Key takeaways​

  • Objects model "a thing with named properties"; arrays model "an ordered list of things" β€” most real data is a combination of the two.
  • const prevents reassigning the binding, not mutating the object or array it points to.
  • === on objects/arrays compares identity, not contents β€” two separately created objects are never ===, even with identical properties.
  • Destructuring and optional chaining (?.) dramatically cut down boilerplate when reading nested data.
  • JSON.stringify/JSON.parse convert between Javascript values and their text representation.

Try it yourself​

  1. Given const users = [{name: 'A', age: 30}, {name: 'B', age: 17}], write an expression that returns just the names of users 18 or older.
  2. Write a function mergeSettings(defaults, overrides) that returns a new object combining both, with overrides winning on any shared key. (Hint: the spread operator works on objects too.)
  3. Destructure { a, b, ...rest } from { a: 1, b: 2, c: 3, d: 4 } and log all three bindings.
Hints
  1. You'll want .filter(...) followed by .map(...) β€” both covered fully in Chapter 5, but you can write the loop version with for...of and an if for now.
  2. { ...defaults, ...overrides } β€” later spreads override earlier ones for the same key.
  3. rest will end up as { c: 3, d: 4 } β€” the rest pattern collects everything not explicitly named.

Summary​

Objects and arrays are the workhorses of every Javascript program β€” almost everything you model, from a user profile to an entire API response, is built from these two shapes. Understanding mutability and reference semantics now will save you from a whole category of "why did this other variable change?!" bugs later. Next, we look at what you can do with arrays once you stop reaching for manual loops: higher-order functions.

Β 

🌟 Join the Javascript360 community

Connect with fellow JS developers, and get free interview prep, React challenges, and DSA guides.

Join on WhatsApp β†’