New: Try Voli The Bear, Fast package manager (and not only) for Windows
Updated Jun 19, 2026 Edit on GitHub

Collections - Arrays & Objects

Real programs deal with many things - a list of users, the fields of a form, the items in a cart. JavaScript has two workhorse collections for this: the array (an ordered list) and the object (a labeled bundle). Get these two fluent and a huge amount of JavaScript opens up.

Arrays: ordered lists

An array is an ordered list of values, written with square brackets. Values can be any type, and you reach into the list by position - counting from 0, not 1.

const fruits = ["apple", "banana", "cherry"];
console.log(fruits[0]);     // first item
console.log(fruits[2]);     // third item
console.log(fruits.length); // how many items
apple
cherry
3

What just happened: fruits[0] is the first element, since array indexes start at zero - universal across most languages. fruits[2] is the third. .length gives the item count. A nonexistent index (fruits[99]) gives undefined, not an error.

You can add and change items:

const fruits = ["apple", "banana"];
fruits.push("cherry");   // add to the end
fruits[0] = "apricot";   // replace the first item
console.log(fruits);
[ 'apricot', 'banana', 'cherry' ]

What just happened: .push(...) appended "cherry" to the end; fruits[0] = "apricot" overwrote the first slot. The array is a const and yet we changed its contents - allowed, for a reason that's the most important idea in this phase (more below).

A taste of array methods: map, filter, reduce

Arrays come with built-in methods that transform lists without manual loops. These three you'll reach for constantly, so meet them now even if they feel like a lot at first.

map makes a new array by transforming every item:

const numbers = [1, 2, 3];
const doubled = numbers.map((n) => n * 2);
console.log(doubled);
[ 2, 4, 6 ]

What just happened: .map(...) walked numbers, ran (n) => n * 2 on each item, and collected the results into a brand-new array. (That's an arrow function - covered properly in Phase 4; for now read it as "given n, give back n * 2.") The original numbers is untouched.

filter makes a new array keeping only the items that pass a test:

const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens);
[ 2, 4, 6 ]

What just happened: .filter(...) kept each item only when n % 2 === 0 ("remainder on dividing by 2 is zero," i.e. even) returned true; the odd numbers were dropped. Again, a new array comes out and the original stays put.

reduce boils a whole array down to a single value:

const numbers = [10, 20, 30];
const total = numbers.reduce((sum, n) => sum + n, 0);
console.log(total);
60

What just happened: .reduce(...) carries a running value (sum) across the list, starting at 0 (the second argument), adding each item on: 0+10, +20, +30, landing on 60. Most powerful and least obvious of the three - mental model: "fold the list into one result, one item at a time."

💡 Key point. map, filter, and reduce all return new values and leave the original array alone. Building new data instead of mutating old data prevents a whole class of bugs, and it reads like a sentence: "take the numbers, filter the evens, map them doubled."

Objects: labeled bundles

Where an array holds values by position, an object holds values by name - a bundle of key: value pairs in curly braces, perfect for representing one "thing" with several properties.

const user = {
  name: "Ada",
  age: 36,
  isAdmin: true,
};
console.log(user.name);     // dot notation
console.log(user["age"]);   // bracket notation
Ada
36

What just happened: user bundles three labeled values, read by key either with a dot (user.name - what you'll use most) or with brackets and the key as a string (user["age"] - needed when the key is in a variable or has unusual characters).

You change and add properties freely:

const user = { name: "Ada" };
user.age = 36;        // add a new property
user.name = "Ada L."; // change an existing one
console.log(user);
{ name: 'Ada L.', age: 36 }

What just happened: Assigning to user.age (a key that didn't exist) added it; assigning to user.name changed it. Objects are open for extension by default.

📝 Terminology. A property is one key: value pair on an object; a method is a property whose value is a function (e.g. console.log is the log method of the console object). Arrays are technically a special kind of object too - why they have methods like .push().

A one-line note on Map and Set

For most "labeled data," a plain object is exactly right. But JavaScript also has two purpose-built collections worth knowing the names of: a Map is like an object but its keys can be any type (not just strings) and it remembers insertion order cleanly; a Set is a list that automatically rejects duplicates. Reach for them when those powers matter; until then, arrays and objects cover most real code.

⚠️ The big one: reference vs. value

This single idea explains the const-but-still-changeable puzzle from earlier, and a bug that bites every JavaScript developer. Pay attention here.

Primitives are copied by value. Objects and arrays are shared by reference. Assign a number or string and you copy the value. Assign an object or array and you copy a reference - a pointer to the same underlying thing. Two names, one object.

const a = { count: 1 };
const b = a;        // b points at the SAME object as a
b.count = 99;
console.log(a.count);
99

What just happened: const b = a did not make a second object - it made b point at the exact same object a points at, so changing b.count also changed a.count. This surprises everyone the first time. (Compare with primitives: let x = 1; let y = x; y = 99; leaves x as 1, because the number was copied.)

This is also why a const array can still be pushed into: const locks the name to one object, but the object's insides stay free to change. const protects the pointer, not the contents.

And it's why two equal-looking objects aren't equal:

console.log({ x: 1 } === { x: 1 });   // two separate objects
const same = { x: 1 };
console.log(same === same);           // the same object
false
true

What just happened: The first comparison is false because those are two different objects that merely look alike - === on objects asks "are these the same object?", not "do they contain the same stuff?" The second is true because both sides are literally the same object. To compare contents, compare the fields yourself (or use a library) - source of countless "but they're the same!" debugging sessions.

🪖 War story. A classic bug: copy an array with const copy = original, tweak copy, and later discover original changed too - they were always the same array. The fix: make a real copy with const copy = [...original] (array) or const copy = { ...original } (object). That ... is the "spread" syntax; it builds a new collection with the old one's items shallow-copied in. Keep it in your back pocket.

Recap

  1. Arrays are ordered lists indexed from 0; .length counts them, .push() appends.
  2. map/filter/reduce transform / select / fold an array into a new value, leaving the original alone.
  3. Objects are key: value bundles read with .dot or ["bracket"] notation; properties can be added and changed freely.
  4. Map (any-type keys) and Set (no duplicates) exist for special cases - know the names.
  5. Reference vs. value: objects and arrays are shared, not copied, on assignment. This explains const arrays you can still mutate, and why {x:1} === {x:1} is false. Copy with [...a] / {...o}.

Next: control flow - the logic that decides which code runs and how often - and functions, which let you name and reuse blocks of behavior.


← Phase 2: Syntax, Values & Types · Guide overview · Phase 4: Control Flow & Functions →

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 2 questions

1. The two workhorse collections are...

2. Objects and arrays are assigned by...