A Practical Guide to JavaScript Array Methods
by Kuligaposten 2026-08-29
A tour of the most useful Array.prototype methods in JavaScript, grouped by what they do: searching, transforming, checking, ordering, and combining.
A Practical Guide to JavaScript Array Methods
Arrays are everywhere in JavaScript, and Array.prototype ships with dozens of methods for searching, transforming, and reshaping them. It's easy to only ever reach for map(), filter(), and a for loop and miss the rest. This post walks through the array methods you'll use most often, grouped by purpose, so you know which tool fits which job.
Mutating vs. Non-Mutating
Before diving in, one distinction matters more than any other: some array methods change the array in place, others return a new array (or value) and leave the original untouched.
const original = [3, 1, 2];
const sorted = [...original].sort(); // copy first, stays safe
console.log(original); // [3, 1, 2]
original.sort(); // mutates in place
console.log(original); // [1, 2, 3]
Knowing which category a method falls into avoids a whole class of bugs, especially when arrays are shared across functions or stored in state (e.g. in React).
| Mutates the array | Returns a new array / value |
|---|---|
push, pop, shift, unshift | map, filter, slice |
splice, sort, reverse | concat, flat, flatMap |
fill, copyWithin | reduce, find, join |
Adding and Removing Elements
const list = [1, 2, 3];
list.push(4); // [1, 2, 3, 4] - add to end
list.pop(); // [1, 2, 3] - remove from end
list.unshift(0); // [0, 1, 2, 3] - add to start
list.shift(); // [1, 2, 3] - remove from start
splice() is the general-purpose tool for inserting or removing elements anywhere in the array:
const months = ["Jan", "March", "April", "June"];
// splice(startIndex, deleteCount, ...itemsToInsert)
months.splice(1, 0, "Feb"); // insert without deleting
console.log(months); // ['Jan', 'Feb', 'March', 'April', 'June']
months.splice(4, 1, "May"); // replace one item
console.log(months); // ['Jan', 'Feb', 'March', 'April', 'May']
If you want the non-mutating equivalent, use slice() combined with spread:
const withMay = [...months.slice(0, 4), "May", ...months.slice(4)];
Searching and Checking
These methods answer questions about an array without transforming it.
const users = [
{ id: 1, name: "Ana", active: true },
{ id: 2, name: "Bo", active: false },
{ id: 3, name: "Cy", active: true },
];
users.find((u) => !u.active); // { id: 2, name: 'Bo', active: false }
users.findIndex((u) => !u.active); // 1
users.some((u) => !u.active); // true - at least one match
users.every((u) => u.active); // false - not all match
users.includes(users[0]); // true - reference equality
find()/findIndex()return the first matching element (or its index), orundefined/-1if nothing matches.some()short-circuits on the firsttrue;every()short-circuits on the firstfalse. Both are efficient for large arrays because they stop early.includes()checks for a value using SameValueZero equality (like===, butNaNequalsNaN). For primitives it's a quick existence check; for objects it only matches by reference.
Since ES2023, findLast() and findLastIndex() search from the end:
users.findLast((u) => u.active); // { id: 3, name: 'Cy', active: true }
Transforming Arrays
map(), filter(), and reduce() are covered in depth in their own posts on this blog, but here's the shape of each for reference:
const numbers = [1, 2, 3, 4, 5];
numbers.map((n) => n * 2); // [2, 4, 6, 8, 10] - same length, transformed
numbers.filter((n) => n % 2 === 0); // [2, 4] - subset that matches
numbers.reduce((acc, n) => acc + n, 0); // 15 - collapsed to one value
Two less common but very handy transformers:
// flat(depth) - flattens nested arrays, depth defaults to 1
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat(); // [1, 2, 3, 4, [5, 6]]
nested.flat(2); // [1, 2, 3, 4, 5, 6]
nested.flat(Infinity); // fully flatten, any depth
// flatMap(fn) - map() then flatten one level, more efficient than doing both
const sentences = ["hello world", "foo bar"];
sentences.flatMap((s) => s.split(" ")); // ['hello', 'world', 'foo', 'bar']
Ordering
const scores = [40, 100, 1, 5, 25];
scores.sort(); // [1, 100, 25, 40, 5] - sorts as strings by default!
scores.sort((a, b) => a - b); // [1, 5, 25, 40, 100] - ascending
scores.sort((a, b) => b - a); // [100, 40, 25, 5, 1] - descending
The default sort() converts elements to strings and compares them lexicographically, which is almost never what you want for numbers. Always pass a comparator for numeric or custom sorting. sort() mutates the array; if you need to preserve the original, copy it first with [...scores] or use toSorted() (ES2023), which returns a new sorted array:
const ascending = scores.toSorted((a, b) => a - b);
reverse() (mutating) and toReversed() (non-mutating, ES2023) work the same way for flipping order.
Combining and Iterating
const a = [1, 2];
const b = [3, 4];
a.concat(b); // [1, 2, 3, 4] - non-mutating
[...a, ...b]; // [1, 2, 3, 4] - same result, spread syntax
a.join("-"); // '1-2' - array to string
"1,2,3".split(","); // ['1', '2', '3'] - string to array (not an array method, but the common pairing)
a.forEach((n) => console.log(n)); // runs a callback per element, returns undefined
forEach() looks similar to map(), but it never returns anything useful and can't be chained. Reach for it only when you want side effects (logging, pushing into an external variable) rather than a transformed array.
Choosing the Right Method
| Need | Method |
|---|---|
| Transform every element | map() |
| Keep some elements | filter() |
| Collapse to one value | reduce() |
| Find one element | find() / findIndex() |
| Check existence | includes(), some() |
| Check all match | every() |
| Run side effects | forEach() |
| Flatten nested arrays | flat() / flatMap() |
| Reorder | sort() / toSorted() |
| Insert/remove anywhere | splice() |
A Chained Example
Real code often chains several of these together:
const orders = [
{ id: 1, total: 42.5, paid: true },
{ id: 2, total: 15.0, paid: false },
{ id: 3, total: 99.99, paid: true },
{ id: 4, total: 5.25, paid: true },
];
const totalPaid = orders
.filter((order) => order.paid)
.map((order) => order.total)
.reduce((sum, total) => sum + total, 0);
console.log(totalPaid.toFixed(2)); // '147.74'
Each step does one clear thing: filter() narrows the data, map() extracts what's needed, and reduce() collapses it to a final result. This pipeline style is usually more readable than an equivalent for loop, though for very large arrays or hot code paths a single loop can be faster since it avoids creating intermediate arrays.
Conclusion
Most everyday array work boils down to a small set of methods: map, filter, reduce, find, some/every, and sort. Knowing which ones mutate the array and which return a fresh copy will save you from a lot of subtle bugs, and knowing the less common ones — flat, flatMap, findLast, toSorted — means you won't reinvent them by hand. Pick the method that names your intent, and the code will read closer to the problem you're solving.