Essential JavaScript Array Methods

AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.

Node.js 22 LTS

There are dozens of array methods, but a dozen cover daily work. Sorting them by whether they mutate the original array prevents most “why did my data change” bugs.

Mutating versus non-mutating

Mutates the array Returns a new result
push pop shift unshift map filter slice concat
splice sort reverse reduce find some every
fill copyWithin flat flatMap toSorted toReversed

If you did not explicitly ask for in-place modification, use the right-hand column.

map: transform each element

const nums = [1, 2, 3];
const doubled = nums.map((n) => n * 2);

console.log(doubled);   // [2, 4, 6]
console.log(nums);      // [1, 2, 3]

The callback’s return values form a new array of the same length. If you also need to drop elements, use filter first or flatMap — returning undefined from map and filtering afterwards is a common anti-pattern.

filter: keep what matches

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

The callback must return a boolean (or something truthy). filter never changes the length of the elements it keeps.

reduce: collapse to one value

const nums = [1, 2, 3, 4];
const total = nums.reduce((sum, n) => sum + n, 0);
console.log(total);   // 10

Always pass the second argument. Without an initial value, reduce starts from the first element and throws TypeError on an empty array.

Grouping is the other classic use:

const words = ["apple", "ant", "banana", "berry"];

const groups = words.reduce((acc, word) => {
  const key = word[0];
  acc[key] ??= [];
  acc[key].push(word);
  return acc;
}, {});

console.log(groups);
// { a: ["apple", "ant"], b: ["banana", "berry"] }

reduce can express sums, counts, grouping, and flattening, but filter().map() is easier to read for filter-then-transform. In Node, Object.groupBy() (Node 21+) is even clearer for grouping.

find, findIndex, some, every

const users = [
  { id: 1, name: "ann", active: true },
  { id: 2, name: "bob", active: false },
];

users.find((u) => u.id === 2);        // the object, or undefined
users.findIndex((u) => !u.active);    // 1, or -1
users.some((u) => u.active);          // true if at least one matches
users.every((u) => u.active);         // false unless all match

some and every short-circuit once the answer is known, which makes them both faster and clearer than filter(...).length > 0.

includes and indexOf

["a", "b"].includes("b");   // true
["a", "b"].indexOf("b");    // 1, or -1

Use includes for existence and indexOf when the position matters. Both compare with ===, so objects compare by reference:

const a = { id: 1 };
[a].includes({ id: 1 });   // false, different object
[a].includes(a);           // true

For objects, use some((x) => x.id === 1).

slice versus splice

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

nums.slice(1, 3);    // [2, 3], original untouched
nums.splice(1, 2);   // returns [2, 3], nums becomes [1, 4, 5]

slice(start, end) copies a range, end excluded. splice(start, deleteCount, ...items) edits in place and returns the removed elements.

The similar names cause real bugs; when reading code, check which one is used before assuming the array is intact.

sort and its string default

const nums = [10, 9, 1, 20];
nums.sort();
console.log(nums);   // [1, 10, 20, 9]

sort() converts elements to strings and compares them lexicographically by default, so 9 lands after 20. Numbers require a comparator:

nums.sort((a, b) => a - b);   // ascending: [1, 9, 10, 20]
nums.sort((a, b) => b - a);   // descending

sort() mutates. To keep the original, use toSorted() (ES2023, Node 20+):

const sorted = nums.toSorted((a, b) => a - b);   // nums unchanged

Also note that sort is stable in modern engines, so equal elements keep their relative order — useful when sorting by one field after another.

flat and flatMap

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

flatMap is map followed by one level of flattening, which fits “one item becomes many” or “one item becomes none”:

const lines = ["a,b", "c"];
lines.flatMap((line) => line.split(","));   // ["a", "b", "c"]

const nums = [1, 2, 3, 4];
nums.flatMap((n) => (n % 2 === 0 ? [n] : []));   // [2, 4]

Chaining

const orders = [
  { amount: 120, paid: true },
  { amount: 80, paid: false },
  { amount: 200, paid: true },
];

const total = orders
  .filter((o) => o.paid)
  .map((o) => o.amount)
  .reduce((sum, n) => sum + n, 0);

console.log(total);   // 320

Chains longer than three steps are easier to debug with intermediate const variables, and you get something to inspect in a debugger.

Common mistakes

1. sort() without a comparator

Numbers sort as strings. Always pass (a, b) => a - b for numbers.

2. reduce without an initial value

Throws on an empty array and surprises readers. Pass 0, {}, or [].

3. Confusing slice and splice

One copies, one mutates. The name difference is a single letter.

4. forEach plus push

That is a map. Using forEach to build an array hides the intent and invites bugs.

5. Splicing while iterating

Indices shift and elements are skipped. Use filter.

6. filter(...).length > 0 for existence

Use some(); it stops at the first match.

7. Chaining map and filter in the wrong order

Filter first to do less work, unless the transform produces the filter condition.

Summary

  • map / filter / reduce / find / slice return new values; splice / sort / reverse / push mutate.
  • Numeric sorts need a comparator; toSorted() keeps the original.
  • Always give reduce an initial value.
  • some() for existence, find() for the element.

Next: objects, destructuring, and reference semantics.