Skip to main content
CodeOath
← All posts

JavaScript60 min total · 16 parts

JavaScript Array and Object Methods Cheat Sheet

Part 3 of 16 · ~4 min

Transforming Arrays

Building an array before you transform it

Sometimes candidates doesn't start out as a friendly array — it starts as something array-like: a NodeList from the DOM, an arguments object, or just a length you want to fill. Three static methods handle that before any of the transformation methods below even apply:

Array.from(document.querySelectorAll(".candidate-row")); // NodeList → a real array with .map, .filter, etc.

Array.from({ length: 5 }, (_, i) => `seat-${i}`); // build one from nothing, using a mapping function
// ["seat-0", "seat-1", "seat-2", "seat-3", "seat-4"]

Array.of(7);   // [7] — one element, the number 7
Array(7);      // [ <7 empty items> ] — NOT the same thing, a completely different constructor call

Array.isArray(candidates); // true — the only reliable check; typeof candidates is just "object"

Array.of(7) and Array(7) look like they should do the same thing and don't: Array(7) with a single number argument sets the length to 7 and leaves seven empty slots, while Array.of(7) always treats its arguments as elements. If you've ever been confused by new Array(count) producing something you couldn't .map() over, that's why — and it's the reason Array.from({ length: n }, mapFn) is the more reliable way to build an array of a known size.

The core transformation methods

Once you have a real array, these are the ones that reshape it:

MethodWhat you get backOriginal arrayTry it on candidates
.map()new array, one output per inputuntouchedcandidates.map(c => c.name) → 5 names
.filter()new array of the matchesuntouchedcandidates.filter(c => c.status === "active") → 3 candidates
.reduce()one folded-down valueuntouchedcandidates.reduce((sum, c) => sum + c.score, 0) → 374
.flat(depth)flattened copy, depth defaults to 1untouched[["node"], ["react", "css"]].flat() → ["node", "react", "css"]
.flatMap()runs .map(), then collapses one level of nesting out of the resultuntouchedcandidates.flatMap(c => c.tags) → all 10 tags, one flat list
.sort(compareFn)same array, reorderedmutatedcandidates.sort((a, b) => a.score - b.score)
.reverse()same array, reversedmutatedcandidates.reverse()
.slice(start, end)shallow copy of a rangeuntouchedcandidates.slice(0, 2) → Priya and Diego
.splice(start, count, ...items)the removed items; edits happen in placemutatedcandidates.splice(1, 1) → removes Diego, returns [Diego]

.slice() and .splice() are the pair everyone mixes up, purely because the names are one letter apart. .slice() only ever reads — it hands you a copy of a stretch of the array and never touches the original. .splice() reaches into the live array and rewrites it directly: elements come out, new ones can go in at the same spot, sometimes both happen in a single call.

The default .sort() trap

Call .sort() with no comparator and it doesn't compare numbers as numbers — it turns every element into text first, then compares that text character by character:

const years = candidates.map(c => c.yearsExperience); // [8, 3, 12, 1, 10]

years.sort();
// [1, 10, 12, 3, 8] — NOT ascending numeric order

It makes sense once you stop thinking about size and start thinking about characters. The comparison walks "1" against "10" one character at a time, and "1" runs out of characters before "10" does — a tie goes to whichever string is shorter, so it sorts first. "3" only ends up after "1", "10", and "12" because the character '3' simply comes later than '1' does. Actual numeric size never once enters into the comparison.

years.sort((a, b) => a - b); // [1, 3, 8, 10, 12] — correct ascending
years.sort((a, b) => b - a); // [12, 10, 8, 3, 1] — correct descending

Make it a rule rather than something you remember case by case: sorting numbers always gets an explicit comparator. Write (a, b) => a - b when you want it ascending; swap which side gets subtracted and you have descending. Leave .sort() bare only when strings, ordered alphabetically, are genuinely the goal.

One more thing worth knowing, since it's easy to assume the opposite: .sort() has been a stable sort since 2019, meaning elements that compare equal keep their original relative order rather than shuffling unpredictably. Sort candidates by role and the two backend candidates that also tie on some secondary property will still come out in whatever order they were already in — you don't need a tiebreaker clause just to preserve that.