JavaScript's array methods are one of those topics where the basics are well-known but the full picture saves significant time. Here's a practical reference, from the commonly used to the ones most developers forget exist.
The big three: map, filter, reduce
map — transform each element
map() creates a new array by applying a function to each element:
const prices = [10, 20, 30];
const withTax = prices.map(price => price * 1.2);
// [12, 24, 36]
const users = [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }];
const names = users.map(user => user.name);
// ['Alice', 'Bob']
map() always returns an array of the same length. If you want to transform and potentially skip elements, use filter() + map() chained, or use reduce().
filter — select elements
filter() creates a new array containing only elements where the function returns true:
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4, 6]
const activeUsers = users.filter(user => user.active);
reduce — accumulate into a single value
reduce() is the most general but also most misunderstood. It takes an accumulator and current element, and returns the new accumulator value:
// Sum
const sum = [1, 2, 3, 4, 5].reduce((acc, curr) => acc + curr, 0);
// 15
// Count occurrences
const words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'];
const counts = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});
// { apple: 3, banana: 2, cherry: 1 }
// Flatten one level
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => [...acc, ...arr], []);
// [1, 2, 3, 4, 5, 6]
The second argument (the 0 or {} above) is the initial accumulator. If you omit it, reduce() uses the first element — which works for numbers but can behave unexpectedly for objects.
Searching: find, findIndex, findLast
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
// First element matching condition
users.find(u => u.id === 2) // { id: 2, name: 'Bob' }
// Index of first match
users.findIndex(u => u.id === 2) // 1
// Last element matching condition (ES2023)
users.findLast(u => u.id < 3) // { id: 2, name: 'Bob' }
// Returns -1 or undefined if not found:
users.find(u => u.id === 99) // undefined
users.findIndex(u => u.id === 99) // -1
Checking: some, every, includes
const scores = [85, 90, 78, 95, 62];
// Does any element match?
scores.some(s => s >= 90) // true
// Does every element match?
scores.every(s => s >= 60) // true
scores.every(s => s >= 80) // false
// Does the array contain this value?
scores.includes(90) // true
[1, 2, NaN].includes(NaN) // true — unlike indexOf
some() returns true as soon as it finds a match. every() returns false as soon as it finds a non-match. Both short-circuit.
Sorting: sort
const fruits = ['banana', 'apple', 'cherry'];
fruits.sort() // ['apple', 'banana', 'cherry'] — lexicographic sort in place
// Numeric sort (IMPORTANT — the default sort is string-based!)
[10, 9, 2, 21].sort() // [10, 2, 21, 9] — WRONG for numbers
[10, 9, 2, 21].sort((a, b) => a - b) // [2, 9, 10, 21] — correct ascending
[10, 9, 2, 21].sort((a, b) => b - a) // [21, 10, 9, 2] — correct descending
// Sort objects by property
users.sort((a, b) => a.name.localeCompare(b.name));
sort() modifies the original array in place. If you need a sorted copy, spread first: [...array].sort(...).
Flattening: flat, flatMap
// flat() — flatten nested arrays
[[1, 2], [3, 4]].flat() // [1, 2, 3, 4]
[[1, [2, 3]], [4]].flat() // [1, [2, 3], 4] — only one level deep
[[1, [2, 3]], [4]].flat(2) // [1, 2, 3, 4] — two levels deep
[[1, [2, [3]]]].flat(Infinity) // [1, 2, 3] — all levels
// flatMap() — map then flatten one level
const sentences = ['hello world', 'foo bar'];
sentences.flatMap(s => s.split(' ')) // ['hello', 'world', 'foo', 'bar']
// Equivalent to sentences.map(s => s.split(' ')).flat()
Slice and splice
const arr = [1, 2, 3, 4, 5];
// slice — returns portion without modifying original
arr.slice(1, 3) // [2, 3] — from index 1 (inclusive) to 3 (exclusive)
arr.slice(-2) // [4, 5] — last 2 elements
arr.slice() // [1, 2, 3, 4, 5] — shallow copy
// splice — modifies original, removes and/or inserts
arr.splice(1, 2) // removes 2 elements starting at index 1, returns [2, 3]
// arr is now [1, 4, 5]
arr.splice(1, 0, 99) // insert 99 at index 1, remove 0 elements
// arr is now [1, 99, 4, 5]
A helpful mnemonic: slicE = no Effect on original, splicE = Effect on original.
Conversion: Array.from, Array.of, join
// Array.from — create array 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] — Set to array
// join — array to string
[1, 2, 3].join(', ') // "1, 2, 3"
[1, 2, 3].join('') // "123"
[1, 2, 3].join('\n') // "1\n2\n3"
// toString is shorthand for join(',')
[1, 2, 3].toString() // "1,2,3"
at() — negative indexing
const arr = [10, 20, 30, 40, 50];
arr.at(0) // 10
arr.at(-1) // 50 — last element
arr.at(-2) // 40 — second to last
Cleaner than arr[arr.length - 1] for accessing from the end.
Structuring complex data transformations
For complex pipelines, chain methods clearly:
const result = users
.filter(user => user.active)
.map(user => ({
id: user.id,
displayName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase()
}))
.sort((a, b) => a.displayName.localeCompare(b.displayName));
This is easier to read and debug than a single complex reduce(). Reserve reduce() for accumulation (building an object, computing a sum) rather than using it as a general-purpose replacement for the other methods.
Array methods are one of those areas where knowing the full toolbox makes your code noticeably cleaner. find() instead of filter()[0], some() instead of a manual loop with a boolean flag, flatMap() instead of map().flat() — small choices that add up across a codebase.
Top comments (0)