Cheatsheet
Every array method, what it returns, what it mutates
One page, every method on Array.prototype plus the static helpers, grouped by what you are trying to do. Each row gives the return value, a plain yes or no on mutation, and a one-line example you can paste into a console. The mutation column is the point: almost every array bug is a method that changed the original when you thought it made a copy.
The mutation rule, and the immutable four
Nine methods mutate the array they are called on: push, pop, shift, unshift, splice, sort, reverse, fill, and copyWithin. Everything else returns something new. Memorize that list and the rest of this page is reference rather than trivia.
Since ES2023 there are non-mutating twins for the awkward ones, and they are the right default in React, Redux, Zustand, or anywhere else state is compared by reference.
const a = [3, 1, 2];
a.sort(); // MUTATES a, returns the same array
a.toSorted(); // [1, 2, 3] - a is untouched
a.toReversed(); // [2, 1, 3] - a is untouched
a.toSpliced(1, 1); // [3, 2] - a is untouched
a.with(0, 99); // [99, 1, 2] - a is untouched
// The old workaround, still fine and still common:
const sorted = [...a].sort(); Gotcha: the four immutable methods are copies, not deep clones. Objects inside the new array are still the same objects. For a real deep copy use structuredClone(arr).
Create and convert arrays
| Method | Returns | Mutates | Example |
|---|---|---|---|
| Array.of() | New array of the arguments | No | Array.of(7) // [7] |
| Array.from() | New array from any iterable or array-like | No | Array.from("ab") // ["a","b"] |
| Array.from(obj, fn) | Mapped array, without an intermediate copy | No | Array.from({length:3}, (_, i) => i) // [0,1,2] |
| Array.fromAsync() | Promise resolving to an array | No | await Array.fromAsync(stream) |
| Array.isArray() | Boolean, and works across realms | No | Array.isArray([]) // true |
| new Array(n) | Holey array of length n, no elements | No | new Array(3).fill(0) // [0,0,0] |
| [...iterable] | New array, shallow | No | [...new Set([1,1,2])] // [1,2] |
| Object.groupBy() | Null-prototype object of string keys to arrays | No | Object.groupBy(users, u => u.role) |
| Map.groupBy() | Map, so keys can be objects | No | Map.groupBy(items, i => i.owner) |
| structuredClone() | Deep copy, including nested objects and Maps | No | structuredClone(rows) |
Gotcha: new Array(3).map(fn) returns three holes untouched, because map skips empty slots. Array.from({length: 3}, fn) does what you meant.
Add and remove elements
This group is where almost all of the mutation lives.
| Method | Returns | Mutates | Example |
|---|---|---|---|
| push() | The new length, not the array | Yes | a.push(4) // 4 |
| pop() | The removed last element | Yes | [1,2].pop() // 2 |
| shift() | The removed first element | Yes | [1,2].shift() // 1 |
| unshift() | The new length | Yes | a.unshift(0) // 4 |
| splice() | Array of what was removed | Yes | a.splice(1, 2, "x") // removed pair |
| toSpliced() | New array with the splice applied | No | [1,2,3].toSpliced(1,1) // [1,3] |
| fill() | The same array | Yes | new Array(3).fill(null) |
| copyWithin() | The same array | Yes | [1,2,3,4].copyWithin(0,2) // [3,4,3,4] |
| a.length = n | n. Truncates or pads with holes | Yes | a.length = 0 // empties in place |
| delete a[i] | true, and leaves a hole. Avoid it | Yes | delete a[1] // length unchanged |
Gotcha: push returns a number, so const next = arr.push(x) gives you a length, not the array. Chaining after push is almost always a bug.
Find and test
| Method | Returns | Mutates | Example |
|---|---|---|---|
| at() | Element at an index, negatives count from the end | No | [1,2,3].at(-1) // 3 |
| indexOf() | First index or -1, strict equality | No | ["a","b"].indexOf("b") // 1 |
| lastIndexOf() | Last matching index or -1 | No | [1,2,1].lastIndexOf(1) // 2 |
| includes() | Boolean, and it finds NaN unlike indexOf | No | [NaN].includes(NaN) // true |
| find() | First matching element or undefined | No | users.find(u => u.id === 3) |
| findIndex() | Index of the first match or -1 | No | users.findIndex(u => u.admin) |
| findLast() | Last matching element or undefined | No | logs.findLast(l => l.level === "error") |
| findLastIndex() | Index of the last match or -1 | No | logs.findLastIndex(l => l.ok) |
| some() | Boolean, stops at the first true | No | nums.some(n => n < 0) // any negative |
| every() | Boolean, stops at the first false. True on an empty array | No | [].every(Boolean) // true |
Gotcha: find returning undefined is indistinguishable from finding a real undefined element. When that matters, use findIndex and compare against -1.
Transform
| Method | Returns | Mutates | Example |
|---|---|---|---|
| map() | New array of the same length | No | [1,2].map(n => n * 2) // [2,4] |
| filter() | New array of elements that passed | No | list.filter(Boolean) // drops falsy |
| reduce() | Whatever the accumulator ends up as | No | nums.reduce((s, n) => s + n, 0) |
| reduceRight() | Same, walking from the end | No | fns.reduceRight((v, f) => f(v), x) |
| flat() | New array flattened one level by default | No | [1,[2,[3]]].flat(Infinity) // [1,2,3] |
| flatMap() | map then flat(1), in one pass | No | rows.flatMap(r => r.tags) |
| flatMap() as filter-map | Return [] to drop, [x] to keep | No | xs.flatMap(x => x.ok ? [x.id] : []) |
| with() | New array with one index replaced | No | [1,2,3].with(1, 9) // [1,9,3] |
Gotcha: reduce without an initial value uses element 0 as the seed and throws on an empty array. Always pass the second argument; it also gives TypeScript something to infer from.
Iterate
| Method | Returns | Mutates | Example |
|---|---|---|---|
| forEach() | undefined. Cannot break or return early | No | rows.forEach(r => log(r)) |
| for...of | Nothing. Supports break, continue, and await | No | for (const r of rows) { ... } |
| entries() | Iterator of [index, value] pairs | No | for (const [i, v] of a.entries()) |
| keys() | Iterator of indices, including holes | No | [...Array(3).keys()] // [0,1,2] |
| values() | Iterator of values, the default iterator | No | const it = a.values() |
| for...in | String keys, including inherited ones. Do not use on arrays | No | for (const k in a) // k is "0", not 0 |
Gotcha: forEach ignores a returned promise, so arr.forEach(async x => await save(x)) finishes instantly and swallows every rejection. Use for...of with await, or Promise.all(arr.map(...)).
Sort and order
| Method | Returns | Mutates | Example |
|---|---|---|---|
| sort() | The same array, sorted in place. Stable since ES2019 | Yes | nums.sort((a, b) => a - b) |
| toSorted() | A new sorted array | No | nums.toSorted((a, b) => b - a) |
| reverse() | The same array, reversed in place | Yes | a.reverse() |
| toReversed() | A new reversed array | No | messages.toReversed() |
| sort() by string | Locale aware ordering with a collator | Yes | names.sort((a, b) => a.localeCompare(b)) |
| sort() multi key | Chain comparisons with logical OR | Yes | rows.sort((a,b) => a.g - b.g || a.n - b.n) |
Gotcha: a bare sort() converts every element to a string first, so [10, 9, 1].sort() gives [1, 10, 9]. Numbers always need a comparator.
Combine, slice, and stringify
| Method | Returns | Mutates | Example |
|---|---|---|---|
| concat() | New array of both, one level flattened | No | [1].concat([2,3]) // [1,2,3] |
| slice() | Shallow copy of a range, end exclusive | No | a.slice(-3) // last three |
| slice() | Full shallow copy with no arguments | No | const copy = a.slice() |
| join() | String. null and undefined become empty | No | ["a","b"].join("-") // "a-b" |
| toString() | Comma joined string | No | [1,2].toString() // "1,2" |
| [...a, ...b] | New array, and works with any iterable | No | [...a, extra, ...b] |
| [...new Set(a)] | Deduplicated array, order preserved | No | [...new Set([1,1,2])] // [1,2] |
| Object.fromEntries() | Object built from pairs | No | Object.fromEntries(a.map(x => [x.id, x])) |
Gotcha: concat flattens one level of array arguments but not of nested elements, so [1].concat([[2]]) gives [1, [2]]. Spread has the same behavior and is easier to read.
Async patterns over arrays
There is no async version of map or filter. You combine map with a promise combinator, and the combinator you choose is the whole decision.
// Parallel, all must succeed. First rejection wins and the rest keep running.
const users = await Promise.all(ids.map(id => fetchUser(id)));
// Parallel, collect every outcome. Never rejects.
const results = await Promise.allSettled(ids.map(fetchUser));
const ok = results.filter(r => r.status === "fulfilled").map(r => r.value);
// First success wins; rejects only if every one fails.
const fastest = await Promise.any(mirrors.map(fetchFrom));
// First settled outcome wins, success or failure. Usually a timeout race.
const raced = await Promise.race([fetchUser(id), timeout(3000)]);
// Sequential, because order or rate limits matter.
const out = [];
for (const id of ids) out.push(await fetchUser(id));
// Async filter: map to predicates in parallel, then zip.
const flags = await Promise.all(items.map(isValid));
const valid = items.filter((_, i) => flags[i]);
// Bounded concurrency without a library: chunk, then run each chunk in parallel.
const chunk = (a, n) =>
Array.from({ length: Math.ceil(a.length / n) }, (_, i) => a.slice(i * n, i * n + n));
for (const batch of chunk(ids, 5)) await Promise.all(batch.map(fetchUser));
// Drain an async iterable straight into an array.
const lines = await Array.fromAsync(readLines(file)); Gotcha: ids.map(async id => ...) returns an array of promises, not values. Forgetting the await Promise.all(...) around it produces an array full of Promise { pending }, which usually surfaces much later as a rendering bug.
Gotcha: callbacks receive three arguments. ["1","7","11"].map(parseInt) gives [1, NaN, 3] because the index is passed as the radix. Wrap it: .map(s => parseInt(s, 10)), or use .map(Number).
Keep going
Typing these correctly is its own skill: the TypeScript cheatsheet covers the utility types, narrowing, and generic constraints that make filter and reduce infer the shapes you actually wanted.
Working with array state inside components? The React hooks cheatsheet covers the dependency and identity rules that make the immutable four worth using, and Zustand vs Redux covers where that state should live.
Reference: MDN Array. More quick references are in the cheatsheet index.