Toolmingo
Guides10 min read

JavaScript Array Methods: Complete Guide with Examples

Master every JavaScript array method with real examples. Covers map, filter, reduce, find, some, every, flat, flatMap, forEach, sort, and more — with TypeScript types, common mistakes, and interview questions.

JavaScript arrays ship with a rich set of built-in methods. Knowing which method to reach for — and how each one behaves at the edges — separates junior code from production-ready code.


Quick-reference table

Method Returns Mutates? Purpose
map new array (same length) No Transform every element
filter new array (≤ length) No Keep elements that pass a test
reduce single value No Accumulate elements into one value
forEach undefined No Side effects per element
find element | undefined No First element matching predicate
findIndex index | -1 No Index of first match
findLast element | undefined No Last element matching predicate
some boolean No True if any element passes
every boolean No True if all elements pass
includes boolean No True if value exists (uses ===)
indexOf index | -1 No First index of a value
flat new array No Flatten nested arrays
flatMap new array No map then flat(1) in one pass
sort same array Yes Sort in place
reverse same array Yes Reverse in place
splice removed elements Yes Add/remove at index
push / pop new length / removed Yes Add/remove at end
shift / unshift removed / new length Yes Add/remove at start
slice new array No Copy a range
concat new array No Merge arrays
join string No Elements to string
fill same array Yes Fill range with value
copyWithin same array Yes Copy segment within array
at element | undefined No Element at index (negatives work)
Array.from new array Create array from iterable
Array.of new array Create array from arguments
Array.isArray boolean Type check

Transforming: map

Creates a new array by applying a function to each element.

const prices = [10, 20, 30];
const withTax = prices.map(p => p * 1.2);
// [12, 24, 36]  — prices unchanged
// TypeScript: map infers the return type
const lengths: number[] = ["hello", "world"].map(s => s.length);
// [5, 5]

Key rules:

  • Always returns an array of the same length.
  • Use map when you need a new array; use forEach when you only want side effects.
  • Never push inside a map callback — that's a forEach.

Filtering: filter

Returns a new array containing only elements where the callback returns truthy.

const scores = [42, 95, 60, 78, 55];
const passing = scores.filter(s => s >= 60);
// [95, 60, 78]

Chain with map:

const users = [
  { name: "Alice", active: true },
  { name: "Bob",   active: false },
  { name: "Carol", active: true },
];

const activeNames = users
  .filter(u => u.active)
  .map(u => u.name);
// ["Alice", "Carol"]

Accumulating: reduce

Reduces an array to a single value by running a callback on each element with an accumulator.

// Syntax: array.reduce(callback, initialValue)
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((acc, n) => acc + n, 0);
// 15

Common reduce patterns

Sum / product:

const total = cart.reduce((sum, item) => sum + item.price, 0);

Count occurrences:

const votes = ["yes", "no", "yes", "yes", "no"];
const tally = votes.reduce((acc, v) => {
  acc[v] = (acc[v] ?? 0) + 1;
  return acc;
}, {});
// { yes: 3, no: 2 }

Group by a key:

const people = [
  { name: "Alice", dept: "Eng" },
  { name: "Bob",   dept: "HR"  },
  { name: "Carol", dept: "Eng" },
];

const byDept = people.reduce((groups, person) => {
  const key = person.dept;
  (groups[key] ??= []).push(person);
  return groups;
}, {});
// { Eng: [{Alice}, {Carol}], HR: [{Bob}] }

Flatten one level (educational — use flat() in practice):

const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5]

Always provide an initial value. Calling reduce on an empty array without one throws a TypeError.


Searching: find, findIndex, findLast

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob"   },
  { id: 3, name: "Carol" },
];

// find — returns the element (or undefined)
const bob = users.find(u => u.id === 2);
// { id: 2, name: "Bob" }

// findIndex — returns the index (or -1)
const idx = users.findIndex(u => u.id === 2);
// 1

// findLast — searches from the end (ES2023)
const lastEven = [1, 2, 3, 4, 5].findLast(n => n % 2 === 0);
// 4

Use find / findLast when you want the element; use findIndex / findLastIndex when you need the position.


Boolean checks: some, every, includes

const ages = [12, 18, 25, 30];

ages.some(a => a >= 18);    // true  — at least one adult
ages.every(a => a >= 18);   // false — not all adults
ages.includes(25);           // true  — 25 exists (uses ===)

Short-circuit behaviour:

  • some stops at the first truthy result.
  • every stops at the first falsy result.

Flattening: flat, flatMap

// flat — default depth is 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 — map then flat(1) in one pass (more efficient)
const sentences = ["hello world", "foo bar"];
const words = sentences.flatMap(s => s.split(" "));
// ["hello", "world", "foo", "bar"]

flatMap is the idiomatic way to produce zero-or-more elements per input element:

// Expand each order into its line items
const lineItems = orders.flatMap(o => o.items);

Sorting: sort

sort mutates the original array and converts elements to strings by default.

// Wrong — lexicographic order
[10, 1, 20, 2].sort();    // [1, 10, 2, 20] ⚠️

// Correct — numeric comparator
[10, 1, 20, 2].sort((a, b) => a - b);   // [1, 2, 10, 20]
[10, 1, 20, 2].sort((a, b) => b - a);   // [20, 10, 2, 1] (descending)

Sort strings with localeCompare:

["banana", "Apple", "cherry"].sort((a, b) =>
  a.localeCompare(b, undefined, { sensitivity: "base" })
);
// ["Apple", "banana", "cherry"]

Sort objects:

users.sort((a, b) => a.name.localeCompare(b.name));

Immutable sort (ES2023 toSorted):

const sorted = arr.toSorted((a, b) => a - b); // original untouched

Slicing and splicing

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

// slice — non-mutating, copies a range
arr.slice(1, 3);   // [2, 3]   (index 1 up to, not including, 3)
arr.slice(-2);     // [4, 5]   (last 2)

// splice — mutating: remove / insert
arr.splice(2, 1);        // removes 1 element at index 2 → arr is now [1,2,4,5]
arr.splice(2, 0, 99);    // inserts 99 at index 2 → [1,2,99,4,5]

Immutable alternatives (ES2023):

arr.toSpliced(2, 1);       // like splice but returns new array
arr.with(2, 99);           // replace element at index 2, returns new array
arr.toReversed();          // like reverse but non-mutating

Iterating: forEach

users.forEach(user => {
  console.log(user.name);
});

Rules:

  • Returns undefined — you cannot chain .map after forEach.
  • Cannot be stopped early (use for...of with break if you need that).
  • Do not use for transformations — use map / filter instead.

Utility methods

// at — supports negative indices (ES2022)
const last = [1, 2, 3].at(-1);   // 3

// join — array to string
["a", "b", "c"].join(", ");       // "a, b, c"
["a", "b", "c"].join("");         // "abc"

// concat — merge without mutation
[1, 2].concat([3, 4], [5]);       // [1, 2, 3, 4, 5]

// fill — fill a range (mutating)
new Array(5).fill(0);              // [0, 0, 0, 0, 0]
[1, 2, 3, 4].fill(0, 1, 3);       // [1, 0, 0, 4]

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

Creating arrays: Array.from, Array.of, spread

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

// Array.of — create array from arguments (unlike Array(3) which makes 3 empty slots)
Array.of(3);         // [3]   — one element
Array(3);            // [,,]  — 3 empty slots ⚠️

// Spread — convert or clone
const clone = [...original];
const merged = [...arr1, ...arr2];

TypeScript typed array methods

interface User {
  id: number;
  name: string;
  active: boolean;
}

const users: User[] = [
  { id: 1, name: "Alice", active: true },
  { id: 2, name: "Bob",   active: false },
];

// map infers return type
const names: string[] = users.map(u => u.name);

// filter with type predicate
const active: User[] = users.filter((u): u is User => u.active);

// reduce with explicit accumulator type
const nameMap: Record<number, string> = users.reduce<Record<number, string>>(
  (acc, u) => ({ ...acc, [u.id]: u.name }),
  {}
);

// find returns User | undefined
const found: User | undefined = users.find(u => u.id === 1);

Chaining methods

const result = products
  .filter(p => p.inStock)           // keep in-stock
  .map(p => ({ ...p, price: p.price * 0.9 }))  // 10% discount
  .sort((a, b) => a.price - b.price)            // cheapest first
  .slice(0, 5);                                 // top 5

Each method (except sort and reverse) returns a new array, making safe chaining possible. For large datasets, consider a single reduce pass or a library like Lodash to avoid creating multiple intermediate arrays.


Common mistakes

Mistake Problem Fix
[10,1,2].sort() Lexicographic, not numeric Pass comparator: .sort((a,b) => a-b)
arr.reduce((acc,x) => acc + x) on empty array TypeError: Reduce of empty array with no initial value Always provide initial value: reduce(fn, 0)
Using map for side effects only Returns unused array; linter warns Use forEach for side effects
Mutating sort on a state variable Triggers unexpected re-renders / bugs Use [...arr].sort(fn) or arr.toSorted(fn)
forEach with async callback Does not await promises Use for...of with await or Promise.all(arr.map(...))
arr.find(x => x === val) Returns undefined silently for primitives Use arr.includes(val) for existence checks
splice when you want slice Mutates original Use slice for non-mutating copy

Async patterns

// Wrong — forEach does not await
await arr.forEach(async item => {
  await fetch(item.url); // fires but forEach doesn't wait
});

// Correct — sequential
for (const item of arr) {
  await fetch(item.url);
}

// Correct — parallel
const results = await Promise.all(arr.map(item => fetch(item.url)));

// Correct — parallel with concurrency limit (batch of 3)
async function batchProcess(arr, fn, batchSize = 3) {
  const results = [];
  for (let i = 0; i < arr.length; i += batchSize) {
    const batch = arr.slice(i, i + batchSize);
    results.push(...await Promise.all(batch.map(fn)));
  }
  return results;
}

Performance notes

  • Avoid forEach + push inside a loop when map does the same job — same performance, clearer intent.
  • Chain wisely: .filter().map() creates two arrays. For large arrays, a single .reduce() pass avoids the intermediate allocation.
  • sort is O(n log n); for already-nearly-sorted data it's fast in practice (V8 uses Timsort).
  • flat(Infinity) on deeply nested arrays can be slow — prefer iterative or recursive approaches if depth is unknown and data is huge.
  • includes vs Set.has: includes is O(n); if you check membership repeatedly, convert to a Set first.
// Repeated includes — O(n²) overall
items.filter(x => bigList.includes(x));

// Better — O(n) overall
const set = new Set(bigList);
items.filter(x => set.has(x));

FAQ

Q: When should I use reduce vs map + filter?
Use reduce when you're producing a non-array result (object, number, string) or need a single pass over the data. Use map + filter chains when readability matters more than micro-performance — they're self-documenting.

Q: Does map skip holes in sparse arrays?
Yes — map, filter, forEach, find, and reduce all skip holes (empty slots). for...of and Array.from do not. This is rarely important in practice; avoid sparse arrays.

Q: What's the difference between find and filter?
find returns the first matching element (or undefined). filter returns all matching elements as a new array. Use find when you expect at most one match.

Q: Is it safe to chain sort directly?
No — sort mutates the original. Either copy first ([...arr].sort(fn)) or use the ES2023 toSorted method.

Q: Can I break out of forEach early?
No. Use for...of with break, or use some / every whose short-circuit behaviour can act as an early exit.

Q: What does flatMap do that map + flat doesn't?
Functionally they're equivalent (flatMap always flattens one level). flatMap is slightly more efficient because it combines both passes internally — worth preferring when both operations are needed.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools