Arrays Are Everywhere
Arrays are the most-used data structure in JavaScript. Whether you're rendering a list of users, processing API responses, or transforming data for a chart — arrays and their methods are what you reach for.
JavaScript arrays come with dozens of built-in methods. This guide covers every method you'll use regularly, organized by what they do.
┌────────────────────────────────────────────────────────┐
│ Array Methods by Category │
│ │
│ Transform: map, flatMap │
│ Filter: filter, find, findIndex │
│ Test: some, every, includes │
│ Reduce: reduce, reduceRight │
│ Search: indexOf, lastIndexOf, findLast │
│ Sort: sort, reverse, toSorted, toReversed │
│ Add/Remove: push, pop, shift, unshift, splice │
│ Combine: concat, flat, flatMap │
│ Iterate: forEach, for...of, entries, keys, values │
│ Create: Array.from, Array.of, fill │
└────────────────────────────────────────────────────────┘
Transformation Methods
map — Transform Every Element
Creates a new array by applying a function to each element. The original array is unchanged.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
// [2, 4, 6, 8, 10]
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
];
const names = users.map((user) => user.name);
// ['Alice', 'Bob']
// map passes three arguments: (element, index, array)
const indexed = ['a', 'b', 'c'].map((char, i) => `${i}: ${char}`);
// ['0: a', '1: b', '2: c']
Common mistake — using map when you don't need the return value:
// BAD — map creates a new array that's never used
users.map((user) => console.log(user.name));
// GOOD — use forEach for side effects
users.forEach((user) => console.log(user.name));
filter — Keep What Matches
Creates a new array with only elements that pass a test.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = numbers.filter((n) => n % 2 === 0);
// [2, 4, 6, 8, 10]
const adults = users.filter((user) => user.age >= 18);
// Remove falsy values
const mixed = [0, 'hello', '', null, 42, undefined, false, 'world'];
const truthy = mixed.filter(Boolean);
// ['hello', 42, 'world']
// Remove duplicates (simple values)
const duped = [1, 2, 2, 3, 3, 3, 4];
const unique = duped.filter((val, idx, arr) => arr.indexOf(val) === idx);
// [1, 2, 3, 4]
// (For large arrays, use new Set([...duped]) instead)
reduce — Accumulate Into One Value
The most powerful array method. Reduces an array to a single value by applying a function against an accumulator.
const numbers = [1, 2, 3, 4, 5];
// Sum
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 15
// Max value
const max = numbers.reduce((acc, n) => (n > acc ? n : acc), -Infinity);
// 5
// Count occurrences
const fruits = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'];
const counts = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
// { apple: 3, banana: 2, cherry: 1 }
// Group by property
const people = [
{ name: 'Alice', dept: 'Engineering' },
{ name: 'Bob', dept: 'Marketing' },
{ name: 'Carol', dept: 'Engineering' },
{ name: 'Dave', dept: 'Marketing' },
];
const byDept = people.reduce((acc, person) => {
const key = person.dept;
if (!acc[key]) acc[key] = [];
acc[key].push(person);
return acc;
}, {});
// {
// Engineering: [{ name: 'Alice'... }, { name: 'Carol'... }],
// Marketing: [{ name: 'Bob'... }, { name: 'Dave'... }]
// }
Note: ES2024 introduced Object.groupBy() which handles the grouping pattern more cleanly:
const byDept = Object.groupBy(people, (person) => person.dept);
Always provide an initial value (second argument to reduce). Without it, the first element becomes the initial accumulator, which often causes bugs:
// BAD — no initial value, fails on empty array
[].reduce((acc, n) => acc + n);
// TypeError: Reduce of empty array with no initial value
// GOOD — always provide initial value
[].reduce((acc, n) => acc + n, 0);
// 0
flatMap — Map + Flatten in One Step
Maps each element, then flattens the result by one level. Perfect for operations that return arrays per element.
const sentences = ['Hello world', 'Goodbye moon'];
const words = sentences.flatMap((s) => s.split(' '));
// ['Hello', 'world', 'Goodbye', 'moon']
// Without flatMap:
const words2 = sentences.map((s) => s.split(' ')).flat();
// Same result, but two iterations instead of one
// Filter and transform in one pass
const nums = [1, 2, 3, 4, 5, 6];
const doubledEvens = nums.flatMap((n) => (n % 2 === 0 ? [n * 2] : []));
// [4, 8, 12] — even numbers doubled, odds removed
// Expand nested data
const orders = [
{ id: 1, items: ['apple', 'banana'] },
{ id: 2, items: ['cherry'] },
];
const allItems = orders.flatMap((order) =>
order.items.map((item) => ({ orderId: order.id, item }))
);
// [
// { orderId: 1, item: 'apple' },
// { orderId: 1, item: 'banana' },
// { orderId: 2, item: 'cherry' }
// ]
Search and Test Methods
find and findIndex
find returns the first element that matches. findIndex returns its index.
const users = [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'user' },
{ id: 3, name: 'Carol', role: 'user' },
];
const admin = users.find((u) => u.role === 'admin');
// { id: 1, name: 'Alice', role: 'admin' }
const idx = users.findIndex((u) => u.name === 'Bob');
// 1
const missing = users.find((u) => u.name === 'Dave');
// undefined
const missingIdx = users.findIndex((u) => u.name === 'Dave');
// -1
ES2023 added findLast and findLastIndex for searching from the end:
const numbers = [1, 2, 3, 4, 3, 2, 1];
numbers.findLast((n) => n > 2); // 3 (the second 3)
numbers.findLastIndex((n) => n > 2); // 4
some and every
some returns true if at least one element passes. every returns true if all elements pass.
const numbers = [1, 2, 3, 4, 5];
numbers.some((n) => n > 4); // true — 5 is > 4
numbers.some((n) => n > 10); // false — none > 10
numbers.every((n) => n > 0); // true — all positive
numbers.every((n) => n > 3); // false — 1 and 2 fail
// Practical: check if user has required permissions
const userPerms = ['read', 'write', 'delete'];
const required = ['read', 'write'];
const hasAccess = required.every((perm) => userPerms.includes(perm));
// true
Both short-circuit — some stops at the first true, every stops at the first false.
includes and indexOf
Simple value lookups (no callback):
const fruits = ['apple', 'banana', 'cherry'];
fruits.includes('banana'); // true
fruits.includes('grape'); // false
fruits.indexOf('banana'); // 1
fruits.indexOf('grape'); // -1
// includes handles NaN correctly, indexOf does not
[1, NaN, 3].includes(NaN); // true
[1, NaN, 3].indexOf(NaN); // -1 (NaN !== NaN)
Sorting
sort — Mutates the Original
sort sorts in place and returns the same array. This is one of the most common gotchas.
const nums = [3, 1, 4, 1, 5, 9, 2, 6];
nums.sort();
// [1, 1, 2, 3, 4, 5, 6, 9]
// BUT: default sort converts to strings!
const big = [10, 9, 80, 100, 3];
big.sort();
// [10, 100, 3, 80, 9] — WRONG! Sorted as strings ("10" < "100" < "3")
// Always provide a comparator for numbers
big.sort((a, b) => a - b); // ascending: [3, 9, 10, 80, 100]
big.sort((a, b) => b - a); // descending: [100, 80, 10, 9, 3]
// Sort objects by property
const users = [
{ name: 'Charlie', age: 35 },
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
];
users.sort((a, b) => a.age - b.age);
// [Alice(25), Bob(30), Charlie(35)]
// String sorting with locale awareness
const words = ['banana', 'Apple', 'cherry'];
words.sort((a, b) => a.localeCompare(b));
// ['Apple', 'banana', 'cherry']
Critical: sort mutates the original array:
const original = [3, 1, 2];
const sorted = original.sort();
console.log(original); // [1, 2, 3] — MUTATED
console.log(sorted); // [1, 2, 3]
console.log(original === sorted); // true — same reference!
// To sort without mutating, use toSorted() (ES2023) or spread:
const safelySorted = [...original].sort();
// or
const safelySorted2 = original.toSorted((a, b) => a - b);
toSorted, toReversed, toSpliced (ES2023)
Immutable versions that return new arrays:
const arr = [3, 1, 2];
const sorted = arr.toSorted((a, b) => a - b);
// sorted: [1, 2, 3]
// arr: [3, 1, 2] — unchanged
const reversed = arr.toReversed();
// reversed: [2, 1, 3]
// arr: [3, 1, 2] — unchanged
const spliced = arr.toSpliced(1, 1, 99);
// spliced: [3, 99, 2]
// arr: [3, 1, 2] — unchanged
┌───────────────────┬────────────────────┬──────────────┐
│ Mutating Method │ Immutable Version │ ES Version │
├───────────────────┼────────────────────┼──────────────┤
│ sort() │ toSorted() │ ES2023 │
│ reverse() │ toReversed() │ ES2023 │
│ splice() │ toSpliced() │ ES2023 │
│ arr[i] = val │ with(index, val) │ ES2023 │
└───────────────────┴────────────────────┴──────────────┘
Flattening
flat — Flatten Nested Arrays
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat(); // [1, 2, 3, 4, [5, 6]] — one level
nested.flat(2); // [1, 2, 3, 4, 5, 6] — two levels
nested.flat(Infinity); // flattens completely, any depth
// Practical: API returns inconsistent data
const responses = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
[],
[{ id: 4 }, { id: 5 }],
];
const allItems = responses.flat();
// [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]
Iteration Methods
forEach — Side Effects Only
Executes a function for each element. Returns undefined — cannot chain.
const users = ['Alice', 'Bob', 'Carol'];
users.forEach((user, index) => {
console.log(`${index + 1}. ${user}`);
});
// 1. Alice
// 2. Bob
// 3. Carol
You cannot break out of forEach. Use for...of or for if you need to break or return:
// BAD — return doesn't stop forEach, it just skips to next iteration
['a', 'b', 'c'].forEach((item) => {
if (item === 'b') return; // only skips 'b', doesn't stop loop
console.log(item);
});
// a, c
// GOOD — use for...of to break
for (const item of ['a', 'b', 'c']) {
if (item === 'b') break;
console.log(item);
}
// a
entries, keys, values — Iterators
const fruits = ['apple', 'banana', 'cherry'];
for (const [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}
// 0: apple
// 1: banana
// 2: cherry
[...fruits.keys()]; // [0, 1, 2]
[...fruits.values()]; // ['apple', 'banana', 'cherry']
Adding and Removing Elements
const arr = [1, 2, 3];
// End
arr.push(4); // [1, 2, 3, 4] — returns new length (4)
arr.pop(); // [1, 2, 3] — returns removed element (4)
// Beginning
arr.unshift(0); // [0, 1, 2, 3] — returns new length (4)
arr.shift(); // [1, 2, 3] — returns removed element (0)
// Middle (splice: start, deleteCount, ...items)
arr.splice(1, 1); // removes 1 element at index 1 → [1, 3]
arr.splice(1, 0, 2); // inserts 2 at index 1 → [1, 2, 3]
arr.splice(1, 1, 20, 30); // replaces index 1 → [1, 20, 30, 3]
┌──────────┬──────────────┬─────────────┬────────────┐
│ Method │ Position │ Mutates? │ Returns │
├──────────┼──────────────┼─────────────┼────────────┤
│ push │ End │ Yes │ New length │
│ pop │ End │ Yes │ Removed el │
│ unshift │ Start │ Yes │ New length │
│ shift │ Start │ Yes │ Removed el │
│ splice │ Any position │ Yes │ Removed [] │
│ concat │ End │ No (new) │ New array │
│ slice │ Range │ No (new) │ New array │
└──────────┴──────────────┴─────────────┴────────────┘
slice — Extract Without Mutating
const arr = [0, 1, 2, 3, 4, 5];
arr.slice(2); // [2, 3, 4, 5] — from index 2 to end
arr.slice(1, 4); // [1, 2, 3] — from index 1 to 3 (exclusive end)
arr.slice(-2); // [4, 5] — last 2 elements
arr.slice(); // [0, 1, 2, 3, 4, 5] — shallow copy
Creating Arrays
// Array.from — convert iterable or array-like to array
Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]
Array.from(document.querySelectorAll('div')); // NodeList to array
// Array.of — create array from arguments (unlike Array constructor)
Array.of(3); // [3]
Array(3); // [undefined, undefined, undefined] (confusing)
// fill — fill with a value
new Array(5).fill(0); // [0, 0, 0, 0, 0]
new Array(3).fill({ x: 1 }); // [{x:1}, {x:1}, {x:1}] — same reference!
// For unique objects per element:
Array.from({ length: 3 }, () => ({ x: 1 }));
// [{x:1}, {x:1}, {x:1}] — different references
Functional Programming Patterns
Chaining Methods
const orders = [
{ id: 1, total: 250, status: 'completed', customer: 'Alice' },
{ id: 2, total: 50, status: 'pending', customer: 'Bob' },
{ id: 3, total: 175, status: 'completed', customer: 'Carol' },
{ id: 4, total: 400, status: 'completed', customer: 'Alice' },
{ id: 5, total: 30, status: 'cancelled', customer: 'Dave' },
];
// Get total revenue from completed orders over $100
const revenue = orders
.filter((o) => o.status === 'completed')
.filter((o) => o.total > 100)
.reduce((sum, o) => sum + o.total, 0);
// 825
// Get formatted list of completed order customers
const customers = orders
.filter((o) => o.status === 'completed')
.map((o) => o.customer)
.filter((name, i, arr) => arr.indexOf(name) === i) // unique
.sort()
.join(', ');
// 'Alice, Carol'
Pipe Pattern
For long chains, a pipe function can improve readability:
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
const processOrders = pipe(
(orders) => orders.filter((o) => o.status === 'completed'),
(orders) => orders.filter((o) => o.total > 100),
(orders) => orders.map((o) => ({ customer: o.customer, total: o.total })),
(orders) => orders.sort((a, b) => b.total - a.total)
);
const result = processOrders(orders);
Replace Imperative Loops
// Imperative
const results = [];
for (let i = 0; i < items.length; i++) {
if (items[i].active) {
results.push(items[i].name.toUpperCase());
}
}
// Functional — same result, more declarative
const results = items
.filter((item) => item.active)
.map((item) => item.name.toUpperCase());
Reducing into Different Shapes
// Array → Object (index by ID)
const users = [
{ id: 'a1', name: 'Alice' },
{ id: 'b2', name: 'Bob' },
];
const byId = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
// { a1: { id: 'a1', name: 'Alice' }, b2: { id: 'b2', name: 'Bob' } }
// Array → Map
const userMap = new Map(users.map((u) => [u.id, u]));
// Flatten + transform in one reduce
const nested = [[1, 2], [3, 4], [5, 6]];
const flatDoubled = nested.reduce((acc, arr) => {
return acc.concat(arr.map((n) => n * 2));
}, []);
// [2, 4, 6, 8, 10, 12]
Performance Considerations
When to Use Which Method
┌────────────────┬────────────────────┬──────────────────────────┐
│ Task │ Best Method │ Why │
├────────────────┼────────────────────┼──────────────────────────┤
│ Transform all │ map │ Returns new array │
│ Keep subset │ filter │ Returns matching items │
│ Find one │ find │ Stops at first match │
│ Check existence│ some / includes │ Short-circuits │
│ Aggregate │ reduce │ Single pass │
│ Side effects │ forEach / for...of │ No return value needed │
│ Break early │ for...of / for │ forEach can't break │
│ Best perf │ for loop │ No function call overhead│
└────────────────┴────────────────────┴──────────────────────────┘
Avoid Unnecessary Iterations
// BAD — 3 iterations over the array
const result = items
.map((item) => transform(item))
.filter((item) => item.valid)
.map((item) => format(item));
// BETTER — 1 iteration with reduce
const result = items.reduce((acc, item) => {
const transformed = transform(item);
if (transformed.valid) {
acc.push(format(transformed));
}
return acc;
}, []);
// ALSO GOOD — flatMap for filter+map in one pass
const result = items.flatMap((item) => {
const transformed = transform(item);
return transformed.valid ? [format(transformed)] : [];
});
Large Arrays — Use for Loops
For performance-critical code on very large arrays (100K+ elements), plain for loops are faster than array methods due to function call overhead:
// Array method — cleaner but slower on huge arrays
const sum = largeArray.reduce((acc, n) => acc + n, 0);
// For loop — verbose but faster for 100K+ elements
let sum = 0;
for (let i = 0; i < largeArray.length; i++) {
sum += largeArray[i];
}
In practice, the difference is negligible for arrays under ~10,000 elements. Prefer readability over micro-optimization.
Set for Membership Checks
If you're calling includes() in a loop, convert to a Set first:
const allowedIds = ['a1', 'b2', 'c3', 'd4', 'e5'];
// BAD — O(n) lookup on every iteration = O(n*m) total
const filtered = items.filter((item) => allowedIds.includes(item.id));
// GOOD — O(1) lookup on every iteration = O(n+m) total
const allowedSet = new Set(allowedIds);
const filtered = items.filter((item) => allowedSet.has(item.id));
Common Recipes
Remove Duplicates
// Simple values
const unique = [...new Set([1, 2, 2, 3, 3, 3])];
// [1, 2, 3]
// Objects by property
function uniqueBy(arr, key) {
const seen = new Set();
return arr.filter((item) => {
const val = item[key];
if (seen.has(val)) return false;
seen.add(val);
return true;
});
}
uniqueBy(users, 'email');
Chunk an Array
function chunk(arr, size) {
return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
arr.slice(i * size, i * size + size)
);
}
chunk([1, 2, 3, 4, 5, 6, 7], 3);
// [[1, 2, 3], [4, 5, 6], [7]]
Intersection and Difference
function intersection(a, b) {
const setB = new Set(b);
return a.filter((item) => setB.has(item));
}
function difference(a, b) {
const setB = new Set(b);
return a.filter((item) => !setB.has(item));
}
intersection([1, 2, 3, 4], [3, 4, 5, 6]); // [3, 4]
difference([1, 2, 3, 4], [3, 4, 5, 6]); // [1, 2]
Zip Two Arrays
function zip(a, b) {
return a.map((val, i) => [val, b[i]]);
}
zip(['a', 'b', 'c'], [1, 2, 3]);
// [['a', 1], ['b', 2], ['c', 3]]
Key Takeaways
- map for transformation — returns a new array of the same length
- filter for subsetting — returns matching elements
- reduce for aggregation — accumulates into any shape (number, object, array)
- find for single lookup — returns first match, stops early
- some/every for boolean checks — short-circuit on first match/fail
- sort mutates — use
toSorted()or spread+sort for immutability - flatMap = map + flat — great for filter+transform in one step
- forEach cannot break — use for...of when you need to stop early
- Always pass a comparator to sort — default sort converts to strings
- Set for fast lookups — convert arrays to Sets when checking membership in loops