cheat sheet

Array Methods

JavaScript arrays have ~30 built-in methods for transforming, searching, and reducing data. Covers mutation, non-mutation, search, boolean, iteration, and static methods with real examples.

#javascript#arrays#languageupdated 04-26-2026

Array Methods

What it is

JavaScript arrays have approximately 30 built-in prototype methods for transforming, searching, and reducing collections of data. Understanding which methods mutate the original array and which return a new one is the key mental model.

Mutation methods

These methods modify the original array in place.

javascript
const arr = [1, 2, 3];

// push / pop — end of array
arr.push(4, 5);       // returns new length: 5; arr = [1,2,3,4,5]
arr.pop();            // returns removed element: 5; arr = [1,2,3,4]

// unshift / shift — start of array
arr.unshift(0);       // returns new length: 5; arr = [0,1,2,3,4]
arr.shift();          // returns removed element: 0; arr = [1,2,3,4]

// splice(start, deleteCount, ...items)
arr.splice(1, 2);     // removes 2 elements at index 1; returns [2,3]; arr = [1,4]
arr.splice(1, 0, 9);  // inserts 9 at index 1; arr = [1,9,4]

// sort — default is lexicographic (string comparison)
[10, 2, 21].sort();                     // [10, 2, 21] (wrong for numbers!)
[10, 2, 21].sort((a, b) => a - b);     // [2, 10, 21] ascending
[10, 2, 21].sort((a, b) => b - a);     // [21, 10, 2] descending

// reverse — reverses in place
[1, 2, 3].reverse(); // [3, 2, 1]

// fill(value, start?, end?)
new Array(5).fill(0);          // [0, 0, 0, 0, 0]
[1, 2, 3, 4].fill(9, 1, 3);   // [1, 9, 9, 4]

.sort() converts elements to strings by default. Always pass a comparator when sorting numbers or objects.

Non-mutation methods (return a new value)

These methods leave the original array unchanged.

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

// map — transform each element
nums.map((n) => n * 2);              // [2, 4, 6, 8, 10]
nums.map((n, i) => `${i}:${n}`);    // ['0:1', '1:2', ...]

// filter — keep elements that pass the test
nums.filter((n) => n % 2 === 0);    // [2, 4]

// reduce(callback, initialValue)
nums.reduce((acc, n) => acc + n, 0); // 15

// reduceRight — same but right to left
[[1, 2], [3, 4]].reduceRight((acc, val) => acc.concat(val), []); // [3,4,1,2]

// flat — flatten nested arrays
[1, [2, [3, [4]]]].flat();     // [1, 2, [3, [4]]] (depth 1)
[1, [2, [3, [4]]]].flat(2);   // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]

// flatMap — map then flat(1)
["hello world", "foo bar"].flatMap((s) => s.split(" "));
// ['hello', 'world', 'foo', 'bar']

// slice(start?, end?) — shallow copy of a portion
nums.slice(1, 3);   // [2, 3]  (end is exclusive)
nums.slice(-2);     // [4, 5]  (negative = from end)
nums.slice();       // [1,2,3,4,5] (full shallow copy)

// concat — join arrays
[1, 2].concat([3, 4], [5]);  // [1, 2, 3, 4, 5]

Search methods

find and findIndex accept a predicate and return the first matching element/index. indexOf and includes test by strict equality; use includes for boolean membership (it handles NaN correctly, unlike indexOf). ES2023 added findLast / findLastIndex to search from the end.

javascript
const people = [
  { name: "Alice", age: 30 },
  { name: "Bob", age: 25 },
  { name: "Carol", age: 35 },
];

// find / findIndex — return first match
people.find((p) => p.age > 28);         // { name: 'Alice', age: 30 }
people.findIndex((p) => p.age > 28);    // 0

// findLast / findLastIndex (ES2023) — search from the right
people.findLast((p) => p.age > 28);        // { name: 'Carol', age: 35 }
people.findLastIndex((p) => p.age > 28);   // 2

// indexOf / lastIndexOf — by strict equality
[1, 2, 3, 2].indexOf(2);       // 1
[1, 2, 3, 2].lastIndexOf(2);   // 3
[1, 2, 3].indexOf(9);          // -1 (not found)

// includes — boolean membership check
[1, 2, 3].includes(2);   // true
[1, 2, 3].includes(9);   // false
[1, NaN].includes(NaN);  // true (unlike indexOf)

Boolean methods

every returns true only when all elements pass the predicate (short-circuits on the first failure); some returns true as soon as any element passes (short-circuits on the first match). Both return false / true respectively for empty arrays.

javascript
const scores = [80, 92, 75, 88];

// every — true only if ALL pass
scores.every((s) => s >= 70);   // true
scores.every((s) => s >= 90);   // false

// some — true if ANY passes
scores.some((s) => s >= 90);    // true
scores.some((s) => s >= 100);   // false

Iteration methods

forEach is for side effects only — it always returns undefined and cannot be chained. For transforming data, use map, filter, reduce, or flatMap instead, which all return a new array or value.

javascript
const colors = ["red", "green", "blue"];

// forEach — side effects only; returns undefined
colors.forEach((color, index) => {
  console.log(`${index}: ${color}`);
});

Output:

text
0: red
1: green
2: blue
javascript
// entries — [index, value] pairs
for (const [i, color] of colors.entries()) {
  console.log(i, color);
}

// keys — indices
for (const i of colors.keys()) {
  console.log(i); // 0, 1, 2
}

// values — values (same as for...of on the array itself)
for (const color of colors.values()) {
  console.log(color);
}

Static methods

Array.from converts any iterable or array-like object (including strings and Sets) into a proper array, optionally applying a mapping function. Array.of creates an array from its arguments, avoiding the confusing new Array(n) sparse-array behaviour. Array.isArray is the reliable type-check.

javascript
// Array.from — create from iterable or array-like
Array.from("hello");               // ['h', 'e', 'l', 'l', 'o']
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]
Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3]

// Array.of — create from arguments (unlike Array() constructor)
Array.of(3);      // [3]     (not a sparse array of length 3)
new Array(3);     // [empty × 3]

// Array.isArray — reliable type check
Array.isArray([]);              // true
Array.isArray(new Array());    // true
Array.isArray("not array");    // false

// Array.fromAsync (ES2024) — create from async iterable
async function* asyncRange(n) {
  for (let i = 0; i < n; i++) yield i;
}
const values = await Array.fromAsync(asyncRange(3)); // [0, 1, 2]

// Also works with async mapping
const doubled = await Array.fromAsync([1, 2, 3], async (n) => n * 2); // [2, 4, 6]

Destructuring and spread

Array destructuring unpacks elements into named variables positionally; the rest element (...rest) collects the remainder into a new array. Spread syntax (...arr) copies elements into another array literal or expands an array into function arguments.

javascript
// Destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first = 1, second = 2, rest = [3, 4, 5]

// Swap values
let a = 1, b = 2;
[a, b] = [b, a];
// a = 2, b = 1

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

// Spread — copy / merge arrays
const copy = [...nums];
const merged = [...arr1, ...arr2, 99];

// Spread into function arguments
Math.max(...[1, 5, 3, 7]); // 7

Chaining pattern

A common real-world pattern: filter → map → reduce.

javascript
const orders = [
  { product: "Widget", quantity: 3, price: 9.99, cancelled: false },
  { product: "Gadget", quantity: 1, price: 49.99, cancelled: true },
  { product: "Doohickey", quantity: 2, price: 14.99, cancelled: false },
];

const total = orders
  .filter((o) => !o.cancelled)              // remove cancelled orders
  .map((o) => o.quantity * o.price)         // compute line totals
  .reduce((sum, lineTotal) => sum + lineTotal, 0); // sum them up

console.log(total.toFixed(2)); // "59.95"

Output:

text
59.95

Gotchas

.sort() default is lexicographic

javascript
[10, 2, 100, 21].sort();
// [10, 100, 2, 21] — sorted as strings: "10" < "100" < "2" < "21"

[10, 2, 100, 21].sort((a, b) => a - b);
// [2, 10, 21, 100] — correct numeric sort

Sparse arrays

javascript
const sparse = [1, , 3]; // empty slot at index 1
sparse.length;            // 3
sparse[1];               // undefined

// Most iteration methods skip empty slots
sparse.forEach((v) => console.log(v)); // logs 1, then 3 (skips the hole)
sparse.map((v) => v * 2);             // [2, empty, 6]

// Array.from fills holes with undefined
Array.from(sparse); // [1, undefined, 3]

.map() vs .forEach()

javascript
// forEach always returns undefined — don't try to chain it
const doubled = [1, 2, 3].forEach((n) => n * 2); // undefined

// map returns a new array
const doubled2 = [1, 2, 3].map((n) => n * 2); // [2, 4, 6]

Mutable methods surprise

javascript
const original = [3, 1, 2];
const sorted = original.sort((a, b) => a - b);
// original is NOW [1, 2, 3] — sort mutates in place
// sorted === original — same reference

// To sort without mutation:
const sortedCopy = [...original].sort((a, b) => a - b);
// Or use the new toSorted() (ES2023):
const sortedCopy2 = original.toSorted((a, b) => a - b);

ES2023 non-mutating counterparts

ES2023 introduced copying versions of the four most-common mutating methods. They return a new array, leaving the original untouched — useful when working with immutable state in React, Redux, or similar patterns.

javascript
// These return new arrays without changing the original (ES2023)
arr.toSorted((a, b) => a - b);  // like sort()
arr.toReversed();               // like reverse()
arr.toSpliced(1, 2);           // like splice()
arr.with(2, 99);               // replace element at index 2 with 99

reduce() — semantics in depth

reduce(callback, initialValue?) is the most general iteration method: every other transforming method (map, filter, flat, every, some) can be expressed in terms of it. The callback receives (accumulator, currentValue, currentIndex, sourceArray) and its return value becomes the next accumulator.

The initialValue argument is not optional in practice — omitting it on an empty array throws TypeError, and on a non-empty array it implicitly seeds the accumulator with the first element, which silently breaks reducers whose return type differs from the element type.

javascript
// Always supply an initial value; never rely on the implicit seed
[1, 2, 3, 4].reduce((acc, n) => acc + n, 0);          // 10
[1, 2, 3, 4].reduce((acc, n) => acc + n);             // 10 (works, but fragile)
[].reduce((acc, n) => acc + n, 0);                    // 0
// [].reduce((acc, n) => acc + n);                    // TypeError: Reduce of empty array with no initial value

// Build an index by key (most common shape)
const users = [
  { id: "u1", name: "Alice" },
  { id: "u2", name: "Bob" },
];
const byId = users.reduce((acc, u) => {
  acc[u.id] = u;
  return acc;
}, {});
// { u1: {...}, u2: {...} }

// Group into buckets
const orders = [
  { id: 1, status: "open" },
  { id: 2, status: "shipped" },
  { id: 3, status: "open" },
];
const groups = orders.reduce((acc, o) => {
  (acc[o.status] ||= []).push(o);
  return acc;
}, {});
// { open: [...], shipped: [...] }

// Partition into [pass, fail]
const [evens, odds] = [1, 2, 3, 4, 5].reduce(
  ([e, o], n) => (n % 2 === 0 ? [[...e, n], o] : [e, [...o, n]]),
  [[], []]
);

ES2024 introduced Object.groupBy() and Map.groupBy() — prefer them over hand-written reduce when grouping into buckets. They preserve key insertion order and read more clearly.

javascript
// Modern grouping
const groupedByStatus = Object.groupBy(orders, (o) => o.status);
const groupedByMap = Map.groupBy(orders, (o) => o.status);

reduceRight — fold from the right

reduceRight walks the array from length - 1 down to 0. It is rarely the right tool — the only times you actually want it are when building right-associative structures (function composition, right-folded lists) or peeling off a trailing element.

javascript
// Function composition right-to-left: compose(f, g, h)(x) === f(g(h(x)))
const compose = (...fns) => (x) =>
  fns.reduceRight((acc, fn) => fn(acc), x);

const addOne = (n) => n + 1;
const double = (n) => n * 2;
const square = (n) => n * n;

const pipeline = compose(addOne, double, square); // square first, then double, then addOne
console.log(pipeline(3)); // ((3*3)*2)+1 === 19

Output:

text
19

flat() and flatMap() — flattening nested data

flat(depth = 1) returns a new array with elements from sub-arrays spread into it up to depth levels deep. Infinity flattens completely. flatMap(fn) is map().flat(1) fused into a single pass — strictly faster than calling them separately because it does not allocate the intermediate array.

javascript
// Flatten an n-deep tree
const tree = [1, [2, [3, [4, [5]]]]];
tree.flat(Infinity);          // [1, 2, 3, 4, 5]
tree.flat();                  // [1, 2, [3, [4, [5]]]]  (depth 1)

// flatMap — return 0, 1, or many elements per input
[1, 2, 3].flatMap((n) => [n, n * 10]);    // [1, 10, 2, 20, 3, 30]

// Return [] to drop an element entirely (filter + map in one)
const lines = ["a", "", "b", "  ", "c"];
const cleaned = lines.flatMap((s) => {
  const trimmed = s.trim();
  return trimmed ? [trimmed] : [];
});
// ['a', 'b', 'c']

// Tokenize and word-split in one pass
["hello world", "foo bar"].flatMap((s) => s.split(" "));
// ['hello', 'world', 'foo', 'bar']

flatMap only flattens one level. If your mapper returns nested arrays, you need an explicit .flat(n) afterwards or a different shape.

findLast / findLastIndex (ES2023)

findLast(predicate) and findLastIndex(predicate) search the array right-to-left and return the first match from the end. Before ES2023 the idiomatic workaround was [...arr].reverse().find(...), which allocates a copy; the new methods short-circuit and allocate nothing.

javascript
const events = [
  { type: "click", t: 1 },
  { type: "hover", t: 2 },
  { type: "click", t: 3 },
  { type: "scroll", t: 4 },
];

// Most recent click
events.findLast((e) => e.type === "click");        // { type: 'click', t: 3 }
events.findLastIndex((e) => e.type === "click");   // 2

// Equivalent pre-ES2023 (allocates a copy):
[...events].reverse().find((e) => e.type === "click");

at() — negative indexing without arithmetic

at(index) returns the element at a position; negative indexes count from the end (-1 is the last element). It removes the need for arr[arr.length - 1] and the arr.length arithmetic that hurts readability.

javascript
const items = ["a", "b", "c", "d"];

items.at(0);    // 'a'
items.at(-1);   // 'd'  (last)
items.at(-2);   // 'c'
items.at(99);   // undefined

// Equivalent to:
items[items.length - 1];   // 'd'  but uglier

at exists on strings and TypedArrays as well, with identical negative-index semantics.

copyWithin() — in-place shuffle (rarely useful)

copyWithin(target, start, end?) copies a slice within the same array to another position, in place — useful only for low-level buffer manipulation. Most code should reach for slice + concatenation instead.

javascript
[1, 2, 3, 4, 5].copyWithin(0, 3);    // [4, 5, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(1, 3, 4); // [1, 4, 3, 4, 5]

sort() — stability and comparator contract

Since ES2019, Array.prototype.sort is guaranteed to be stable: equal elements preserve their original relative order across every JavaScript engine. Before that, V8 used an unstable QuickSort variant for arrays of length > 10, which silently broke code that relied on sub-sorting (e.g. sort by date, then by name to break ties).

javascript
const people = [
  { name: "Alice", age: 30, joined: 2020 },
  { name: "Bob",   age: 30, joined: 2018 },
  { name: "Carol", age: 25, joined: 2019 },
];

// Stable: sort by age preserves original join order for ties
const sorted = [...people].sort((a, b) => a.age - b.age);
// Carol (25), then Alice (2020) before Bob (2018) — wait, original order is Alice→Bob→Carol
// so after age-sort: Carol, Alice, Bob (Alice came before Bob in input)

Comparator return-value contract

A comparator (a, b) => number must return:

  • negativea before b
  • positivea after b
  • zero → equal (no reorder under stable sort)
javascript
// Common comparators
const byNumberAsc  = (a, b) => a - b;
const byNumberDesc = (a, b) => b - a;

// Strings — DO NOT use a - b on strings, it returns NaN
const byString = (a, b) => a.localeCompare(b);                                  // locale-aware
const byStringCI = (a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }); // case-insensitive

// Multi-key sort: primary then tiebreaker
const byAgeThenName = (a, b) =>
  a.age - b.age || a.name.localeCompare(b.name);

[...people].sort(byAgeThenName);

Returning a boolean from a comparator ((a, b) => a > b) is the most common sort bug. true coerces to 1, false to 0 — the "equal" return is poisoned, so stability gives you the wrong tiebreak order. Always return a number.

Iterator helpers (ES2025)

ES2025 added lazy iterator-helper methods on the Iterator prototype: map, filter, take, drop, flatMap, reduce, forEach, some, every, find, and toArray. Unlike array methods, they do not allocate intermediate arrays — each value flows through the pipeline one at a time.

javascript
// Without helpers — allocates 3 intermediate arrays
const result1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  .filter((n) => n % 2 === 0)
  .map((n) => n * n)
  .slice(0, 3);

// With helpers — single pass, no intermediate arrays
const result2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  .values()                           // get iterator
  .filter((n) => n % 2 === 0)         // lazy
  .map((n) => n * n)                  // lazy
  .take(3)                            // stops the pipeline after 3 items
  .toArray();                         // [4, 16, 36]

Iterator helpers shine on infinite or expensive iterators:

javascript
function* naturals() {
  let i = 1;
  while (true) yield i++;
}

const first10Squares = naturals()
  .map((n) => n * n)
  .take(10)
  .toArray();
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Check feature support before relying on it — Node 22+ and current evergreen browsers have shipped this; older runtimes need the core-js polyfill.

Array.from with a mapping function

Array.from(iterable, mapFn) and Array.from({ length: n }, mapFn) both apply the mapping during construction — strictly more efficient than Array.from(...).map(...) because there is no intermediate array.

javascript
// Range [0, n)
Array.from({ length: 5 }, (_, i) => i);             // [0, 1, 2, 3, 4]
// Inclusive range [start, end]
const range = (start, end) =>
  Array.from({ length: end - start + 1 }, (_, i) => start + i);
range(3, 7);                                         // [3, 4, 5, 6, 7]

// Zero-filled matrix
const matrix = Array.from({ length: 3 }, () =>
  Array.from({ length: 3 }, () => 0)
);

// Convert NodeList to array (browser)
Array.from(document.querySelectorAll("a"), (el) => el.href);

// Codepoints of a string (Unicode-aware unlike .split(''))
Array.from("café 🚀");          // ['c', 'a', 'f', 'é', ' ', '🚀']
"café 🚀".split("");            // ['c', 'a', 'f', 'é', ' ', '\uD83D', '\uDE80']  (broken surrogate)

new Array(3) creates a sparse array of length 3 with no own indexed properties — iteration methods skip the holes. Array.from({ length: 3 }) creates a dense array of three undefined slots. Prefer the latter for predictable iteration.

Typed arrays — fixed-width binary buffers

A TypedArray (Int8Array, Uint8Array, Float32Array, …) is a fixed-length, fixed-element-size view onto an ArrayBuffer. Use them for binary protocols, image/audio data, and any case where the GC overhead of boxing every number into a JS array would dominate.

javascript
// Allocate a 1024-byte buffer; create views into it
const buffer = new ArrayBuffer(1024);
const u8  = new Uint8Array(buffer);     // 1024 bytes, view as unsigned 8-bit ints
const f32 = new Float32Array(buffer);   // 256 floats, same backing memory
const dv  = new DataView(buffer);       // mixed-endian, mixed-width reads

u8[0] = 255;
console.log(u8[0]);              // 255
console.log(f32[0]);             // NaN — first 4 bytes are 0xFF 0x00 0x00 0x00

// TypedArrays share Array's iteration methods (with some restrictions)
new Uint8Array([1, 2, 3, 4]).reduce((a, b) => a + b, 0);   // 10
new Int32Array(5).fill(7);                                 // Int32Array [7, 7, 7, 7, 7]

Output:

text
255
NaN

Differences from Array:

  • Fixed length — push, pop, shift, unshift do not exist.
  • Element type enforced — assigning 1.7 to a Uint8Array slot truncates to 1.
  • No concat — use set with offset to copy slices in.
  • slice() copies the bytes; subarray() shares the backing buffer (zero-copy view).

Performance characteristics

A short reference for the cost class of the most common operations on a large Array (length N). All ranges are amortized.

OperationCostNotes
arr[i] read/writeO(1)indexed dense access
push / popO(1) amortizedback of array, no shift
shift / unshiftO(N)every other element re-indexes
splice(i, …)O(N - i)shifts tail
concatO(N + M)allocates new array
sliceO(N)shallow copy
indexOf / includesO(N)linear scan
sortO(N log N)stable since ES2019
map / filter / forEachO(N)one allocation
reduceO(N)accumulator allocation depends on shape

Two practical consequences:

  • For O(1) front-of-queue removal, use a linked list shape or rotate an index, not shift.
  • For "does it contain", a Set (O(1) average) beats Array.includes (O(N)) once the array is more than a few dozen items.
javascript
// Anti-pattern — O(N²) for an N-element loop
function uniqueSlow(arr) {
  const out = [];
  for (const x of arr) if (!out.includes(x)) out.push(x);
  return out;
}

// O(N) — use a Set
function uniqueFast(arr) {
  return [...new Set(arr)];
}

Common pitfalls

  1. Reusing the index in forEach/map while mutatingarr.forEach((x, i, a) => a.push(x)) is an infinite loop. The iterator does not snapshot the length.
  2. Relying on forEach for async work[1,2,3].forEach(async (n) => await work(n)) does not wait; the function returns before any await settles. Use for...of with await instead.
  3. Array.from(arrayLike).length vs Object.keys(arrayLike).lengtharguments, NodeList, and { length: 5 } are array-likes; Object.keys returns own enumerable property names, which may not match length.
  4. splice returns the removed items, not the array — chaining .splice(0, 1) onto further array methods rarely works.
  5. sort on string-shaped numbers["10", "2", "1"].sort() returns ["1", "10", "2"], which looks numeric but is lexicographic.
  6. indexOf(NaN) always returns -1NaN !== NaN. Use includes (which uses SameValueZero) for NaN membership.
  7. Spread on huge arrays blows the stackMath.max(...new Array(1e6).fill(1)) throws RangeError: Maximum call stack size exceeded. Use a reduce.
  8. fill with an object referencenew Array(3).fill({}) makes three slots pointing at the same object; mutating one mutates all. Use Array.from({ length: 3 }, () => ({})) for independent objects.
  9. new Array(n) vs Array.of(n)new Array(3) is [empty × 3]; Array.of(3) is [3]. Confusing them produces sparse arrays.
  10. Chained iteration cost.filter().map().filter().map() walks the array four times and allocates three intermediate arrays. For very large arrays, fold into a single reduce or use ES2025 iterator helpers.

Real-world recipes

Group, sum, and sort

A common analytics pattern: bucket records by a key, sum a numeric field per bucket, then emit a sorted leaderboard.

javascript
const sales = [
  { region: "EU", amount: 120 },
  { region: "US", amount: 80 },
  { region: "EU", amount: 200 },
  { region: "APAC", amount: 50 },
  { region: "US", amount: 175 },
];

const totals = sales.reduce((acc, s) => {
  acc[s.region] = (acc[s.region] ?? 0) + s.amount;
  return acc;
}, {});

const leaderboard = Object.entries(totals)
  .map(([region, total]) => ({ region, total }))
  .sort((a, b) => b.total - a.total);

console.log(leaderboard);

Output:

text
[
  { region: 'EU', total: 320 },
  { region: 'US', total: 255 },
  { region: 'APAC', total: 50 }
]

Chunk an array into batches of N

Useful for paginated APIs, bulk-insert batches, and worker pools. The function below avoids slice allocations by building each chunk with Array.from.

javascript
function chunk(arr, size) {
  if (size <= 0) throw new RangeError("size must be > 0");
  const out = [];
  for (let i = 0; i < arr.length; i += size) {
    out.push(arr.slice(i, i + size));
  }
  return out;
}

console.log(chunk([1, 2, 3, 4, 5, 6, 7], 3));

Output:

text
[ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7 ] ]

Deduplicate by a key

new Set deduplicates by SameValueZero on primitives. For objects, build a map keyed by the dedup field and take the values.

javascript
const items = [
  { id: 1, v: "a" },
  { id: 2, v: "b" },
  { id: 1, v: "c" },
];

// Last write wins
const dedup = Object.values(
  items.reduce((acc, x) => ((acc[x.id] = x), acc), {})
);

console.log(dedup);

Output:

text
[ { id: 1, v: 'c' }, { id: 2, v: 'b' } ]

Zip two arrays into pairs

There is no built-in zip, but Array.from({ length }) + index is the idiomatic three-line equivalent.

javascript
const zip = (a, b) =>
  Array.from({ length: Math.min(a.length, b.length) }, (_, i) => [a[i], b[i]]);

console.log(zip(["a", "b", "c"], [1, 2, 3, 4]));

Output:

text
[ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]

Window / sliding pairs

Iterate consecutive (prev, curr) pairs — common in time-series gap detection, diff display, and undo stacks.

javascript
function* pairs(arr) {
  for (let i = 1; i < arr.length; i++) yield [arr[i - 1], arr[i]];
}

const ts = [10, 12, 18, 19, 30];
for (const [prev, curr] of pairs(ts)) {
  console.log(`gap ${curr - prev}`);
}

Output:

text
gap 2
gap 6
gap 1
gap 11

Reservoir sampling — pick K random items from a stream

When the array is too large to shuffle, reservoir sampling picks K items uniformly at random in a single pass.

javascript
function sample(arr, k) {
  const reservoir = arr.slice(0, k);
  for (let i = k; i < arr.length; i++) {
    const j = Math.floor(Math.random() * (i + 1));
    if (j < k) reservoir[j] = arr[i];
  }
  return reservoir;
}

const picked = sample(Array.from({ length: 1000 }, (_, i) => i), 5);
console.log(picked.length);

Output:

text
5

Transpose a matrix

A matrix is an array of arrays. Array.from + index is a one-liner.

javascript
const m = [
  [1, 2, 3],
  [4, 5, 6],
];

const t = m[0].map((_, c) => m.map((row) => row[c]));
console.log(t);

Output:

text
[ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]

Immutable update of a deep array

Pre-ES2023 you had to slice-copy at every level. With with() and toSpliced() the depth-1 case is a one-liner; deeper updates still need recursion.

javascript
const state = [
  { id: "a", count: 1 },
  { id: "b", count: 2 },
  { id: "c", count: 3 },
];

// Replace the element at index 1 with an updated copy — ES2023
const next = state.with(1, { ...state[1], count: state[1].count + 1 });
console.log(state[1].count, next[1].count);

Output:

text
2 3