webdev.complete
📦 Data: Arrays, Objects, Maps
JavaScript
Lesson 29 of 117
35 min

Arrays Deep

map, filter, reduce, find, some, every, flat, group, immutable copies.

Arrays are how JS holds an ordered list of anything. The reason modern JS feels so different from older code is the explosion of array methods. Once you internalize map, filter, and reduce, you stop writing for-loops for almost every data task.

Making and reading arrays

js
const fruits = ["apple", "pear", "kiwi"];

fruits.length        // 3
fruits[0]            // "apple"
fruits[fruits.length - 1]   // "kiwi" (last)
fruits.at(-1)        // "kiwi" - negative index, ES2022
fruits.at(0)         // "apple"

fruits.includes("pear")     // true
fruits.indexOf("kiwi")      // 2
fruits.indexOf("missing")   // -1
Use .at() for negative indexing
Older code does arr[arr.length - 1]. .at(-1) is shorter and works for any negative offset.

The three you'll use every day

map: transform each item

js
const nums = [1, 2, 3, 4];
const doubled = nums.map(n => n * 2);
// [2, 4, 6, 8]

const users = [{ name: "Ada" }, { name: "Grace" }];
const names = users.map(u => u.name);
// ["Ada", "Grace"]

filter: keep some items

js
const nums = [1, 2, 3, 4, 5];
const evens = nums.filter(n => n % 2 === 0);
// [2, 4]

const users = [{ name: "Ada", active: true }, { name: "Bob", active: false }];
const active = users.filter(u => u.active);
// [{ name: "Ada", active: true }]

reduce: roll the array into one value

js
const nums = [1, 2, 3, 4];

const total = nums.reduce((sum, n) => sum + n, 0);
// 10

// reduce can build any shape, not just numbers
const items = ["apple", "pear", "apple", "kiwi", "pear", "apple"];
const counts = items.reduce((acc, item) => {
  acc[item] = (acc[item] ?? 0) + 1;
  return acc;
}, {});
// { apple: 3, pear: 2, kiwi: 1 }
Object.groupBy beats this reduce
Modern JS has Object.groupBy (ES2024) for exactly this pattern. See below.

Search methods: find, findLast, some, every

js
const users = [
  { id: 1, name: "Ada",  active: true  },
  { id: 2, name: "Bob",  active: false },
  { id: 3, name: "Cleo", active: true  },
];

users.find(u => u.id === 2);
// { id: 2, name: "Bob", ... }

users.findLast(u => u.active);
// { id: 3, name: "Cleo", ... }

users.some(u => !u.active);   // true   - at least one inactive?
users.every(u => u.id > 0);   // true   - all positive?

flat and flatMap

js
[1, [2, [3, [4]]]].flat();      // [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]

// flatMap = map + flat(1). Useful for "fan out"
const sentences = ["hi there", "how are you"];
sentences.flatMap(s => s.split(" "));
// ["hi", "there", "how", "are", "you"]

Object.groupBy: the new toy

ES2024 added a clean way to bucket an array by some key:

js
const transactions = [
  { id: 1, type: "debit",  amount: 100 },
  { id: 2, type: "credit", amount: 200 },
  { id: 3, type: "debit",  amount: 50  },
];

const grouped = Object.groupBy(transactions, t => t.type);
// {
//   debit:  [ {id:1,...}, {id:3,...} ],
//   credit: [ {id:2,...} ],
// }

Sorting without surprises

arr.sort() mutates and converts everything to strings by default. [10, 2, 1].sort() gives [1, 10, 2]. Always pass a comparator:

js
const nums = [10, 2, 30, 4];

nums.sort();                 // [10, 2, 30, 4] sorted as strings: [10, 2, 30, 4]
nums.sort((a, b) => a - b);  // [2, 4, 10, 30] numeric ascending
nums.sort((a, b) => b - a);  // [30, 10, 4, 2] descending

const users = [{ name: "Bob" }, { name: "Ada" }];
users.sort((a, b) => a.name.localeCompare(b.name));

Immutable copies: toSorted, toReversed, with

ES2023 added methods that return a new array instead of mutating in place. Way safer:

js
const nums = [3, 1, 4, 1, 5];

nums.toSorted();         // [1, 1, 3, 4, 5]  - nums is unchanged
nums.toReversed();       // [5, 1, 4, 1, 3]  - nums is unchanged
nums.with(0, 99);        // [99, 1, 4, 1, 5] - replace index 0

nums.toSpliced(1, 2);    // [3, 5]            - remove 2 from index 1

// Old (mutating) versions still exist
nums.sort();      // mutates
nums.reverse();   // mutates

Spread and destructuring

js
// Spread to copy or combine
const a = [1, 2];
const b = [3, 4];
const both = [...a, ...b];     // [1, 2, 3, 4]
const copy = [...a];           // shallow clone

// Destructuring
const [first, second] = [10, 20, 30];
// first = 10, second = 20

// Skip with commas
const [, , third] = [10, 20, 30];   // third = 30

// Collect the rest
const [head, ...tail] = [1, 2, 3, 4];
// head = 1, tail = [2, 3, 4]

// Defaults
const [x = 0, y = 0] = [5];   // x = 5, y = 0

Try it: transform a list of users

const users = [
  { id: 1, name: "Ada",  age: 36, role: "admin",  active: true  },
  { id: 2, name: "Bob",  age: 22, role: "viewer", active: false },
  { id: 3, name: "Cleo", age: 41, role: "editor", active: true  },
  { id: 4, name: "Dane", age: 30, role: "viewer", active: true  },
  { id: 5, name: "Evi",  age: 28, role: "editor", active: false },
];

// Names of active users
const activeNames = users
  .filter(u => u.active)
  .map(u => u.name);
console.log("active names:", activeNames);

// Average age
const avg = users.reduce((sum, u) => sum + u.age, 0) / users.length;
console.log("average age:", avg);

// Group by role
const byRole = Object.groupBy(users, u => u.role);
console.log("by role:", byRole);

// Sort by age, immutably
const sorted = users.toSorted((a, b) => a.age - b.age);
console.log("youngest first:", sorted.map(u => u.name + " " + u.age));

// Last active user
const lastActive = users.findLast(u => u.active);
console.log("last active:", lastActive?.name);

// Challenge: try writing one that gets the average age of editors only.

Quiz

Quiz1 / 4

What does [1,2,3].map(n => n * 2) return?

Recap

  • map transforms, filter keeps, reduce rolls up.
  • Use find/findLast for one item, some/everyfor "any/all" tests.
  • Always pass a comparator to sort. Prefer toSorted for new arrays.
  • Spread copies, destructuring unpacks, ...rest in destructuring collects the tail.
  • Object.groupBy and .at(-1) are modern conveniences worth memorizing.
Built with Next.js, Tailwind & Sandpack.
Learn. Build. Ship.