Once you can hold a list of values in an array, the next thing you’ll want is to do something with all of them at once — turn every price into a discounted price, keep only the items that are in stock, add up a column of numbers. You could reach for a manual loop every time, and there’s nothing wrong with that. But JavaScript arrays come with a whole toolbox of built-in methods that handle these patterns for you, in a way that’s shorter, clearer, and far easier to read once you know them.
This is one of those topics where a handful of methods cover the vast majority of real code. We’ll focus on those, show exactly what each one returns, and point out the gotchas that trip people up. If you’re still shaky on the basics, the arrays and objects article is the place to start, and the loops article shows the manual approach these methods replace.
What is an array method?
An array method is just a function attached to an array. You call it with the dot syntax — array.method(...) — and it does some work involving the array’s contents. We already met a few of the simple ones (push, pop) in the arrays article. This time we’re after the iteration methods: the ones that walk through every item and let you supply a small function describing what to do with each.
That “small function you supply” is the key idea. Most of these methods take a callback — a function you hand over, which the method runs once for each item. If callbacks feel unfamiliar, a quick reread of the functions article will pay off here, because arrow functions show up in nearly every example below.
const numbers = [1, 2, 3, 4];
// The callback runs once per item, receiving that item as `n`
numbers.forEach((n) => {
console.log(n); // logs 1, then 2, then 3, then 4
});
forEach is the simplest of the bunch: it runs your callback for every item and returns nothing. It’s a clean replacement for a basic for loop when all you want is to act on each item — log it, append it to the page — without building a new value.
map: transform every item
map is probably the method you’ll use most. It builds a brand-new array by running your callback on each item and collecting whatever the callback returns. The original array is left untouched.
const prices = [100, 250, 80];
const withTax = prices.map((price) => price * 1.1);
console.log(withTax); // [110, 275, 88.00000000000001]
console.log(prices); // [100, 250, 80] — unchanged
One item in, one item out — map always returns a new array of the same length. That’s the mental model: it maps each input to a corresponding output. It shines when you want to reshape data, like pulling one field out of a list of objects:
const users = [
{ name: "John Doe", age: 28 },
{ name: "Jane Doe", age: 34 },
];
const names = users.map((user) => user.name);
console.log(names); // ["John Doe", "Jane Doe"]
map always returns a new array — use the result
The most common map mistake is calling it but ignoring what it gives back. map does not change the original array; it returns a new one. If you write prices.map(...) on its own line and then keep using prices, nothing will have changed. Always capture the result: const updated = prices.map(...). And if you’re not using the returned array at all — you only want a side effect like logging — that’s a sign you wanted forEach, not map.
filter: keep only what you want
filter also returns a new array, but instead of transforming items, it selects them. Your callback returns true to keep an item or false to drop it. The result holds only the items that passed the test — so it can be shorter than the original (or empty, or the full length).
const numbers = [4, 9, 12, 3, 20, 7];
const big = numbers.filter((n) => n >= 10);
console.log(big); // [12, 20]
The callback here is a condition — anything that evaluates to true or false. That makes filter perfect for things like “only in-stock products” or “only completed tasks”:
const products = [
{ name: "Keyboard", inStock: true },
{ name: "Mouse", inStock: false },
{ name: "Monitor", inStock: true },
];
const available = products.filter((p) => p.inStock);
console.log(available.length); // 2
A handy mental rule: map keeps the length and changes the items; filter keeps the items and changes the length. They pair up beautifully — filter down to what matters, then map it into the shape you need.
reduce: boil a list down to one value
reduce is the most powerful and the most intimidating of the three, but the idea behind it is simple: it takes a whole array and reduces it to a single value — a sum, a maximum, a combined object, whatever you’re building toward.
It works by carrying an accumulator along as it walks the array. Your callback receives the accumulator-so-far and the current item, and returns the new accumulator. You also give reduce a starting value as its second argument.
const numbers = [10, 20, 30, 40];
const total = numbers.reduce((sum, n) => sum + n, 0);
// ^acc ^item ^start value
console.log(total); // 100
Step by step: it starts with sum = 0, then 0 + 10 = 10, then 10 + 20 = 30, then 30 + 30 = 60, then 60 + 40 = 100. The final accumulator is the answer. The same pattern adds up a cart total from objects:
const cart = [
{ name: "Keyboard", price: 250000 },
{ name: "Mouse", price: 120000 },
];
const total = cart.reduce((sum, item) => sum + item.price, 0);
console.log(total); // 370000
Always pass a starting value to reduce
The second argument to reduce — the starting accumulator — is technically optional, but you should almost always provide it. Without it, reduce uses the first array element as the initial accumulator and starts iterating from the second, which behaves differently and, crucially, throws an error on an empty array. Passing 0 (for sums), [] (when building an array), or {} (when building an object) makes the intent obvious and keeps empty arrays safe. When in doubt, pass the starting value.
find and findIndex: get the first match
Sometimes you don’t want a filtered list — you want one specific item. find returns the first element for which your callback returns true, and stops looking as soon as it finds one. If nothing matches, it returns undefined.
const users = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Jane Doe" },
];
const user = users.find((u) => u.id === 2);
console.log(user.name); // "Jane Doe"
const missing = users.find((u) => u.id === 99);
console.log(missing); // undefined
findIndex is the sibling that returns the position of the first match instead of the item itself — handy when you need to update or remove that element. It returns -1 if nothing matches.
const index = users.findIndex((u) => u.id === 2);
console.log(index); // 1
The difference between filter and find is worth stating plainly: filter always returns an array (possibly with many items, possibly empty), while find returns a single item (or undefined). If you only need one, find is both clearer and faster, since it stops at the first hit.
some and every: ask a yes/no question
These two return a plain boolean — true or false — which makes them perfect for checks and guards. some asks “does at least one item pass the test?” and every asks “do all items pass?”
const ages = [22, 17, 31, 40];
const hasMinor = ages.some((age) => age < 18);
console.log(hasMinor); // true — 17 is under 18
const allAdults = ages.every((age) => age >= 18);
console.log(allAdults); // false — 17 fails the test
Reach for these whenever you’re validating a list: “is any field empty?” (some), “are all the form inputs valid?” (every). They’re far cleaner than writing a loop with a flag variable, and like find, they short-circuit — some stops at the first true, every stops at the first false.
includes and indexOf: is this value in the array?
For a simple “is this value present?” check on an array of primitives (strings, numbers), includes is the most direct answer. It returns a boolean.
const fruits = ["apple", "banana", "cherry"];
console.log(fruits.includes("banana")); // true
console.log(fruits.includes("mango")); // false
indexOf answers a slightly different question — where is the value? — returning its index, or -1 if it’s absent.
console.log(fruits.indexOf("cherry")); // 2
console.log(fruits.indexOf("mango")); // -1
includes vs find — values vs conditions
Use includes when you’re checking for a known value (includes("banana")). Use find or some when you need a condition (find(u => u.id === 2)). includes compares with strict equality, so it works great for strings and numbers but won’t help you match objects by a property — for that you want find/some. Picking the right one keeps your code short and your intent obvious.
sort: ordering items (and its number trap)
sort reorders the array in place (it mutates the original) and also returns it. With no arguments it sorts as strings, which is exactly the surprise most people hit with numbers:
const nums = [10, 1, 5, 20, 3];
nums.sort();
console.log(nums); // [1, 10, 20, 3, 5] — NOT what you expected!
Because the default sort compares items as text, "10" comes before "3" (just like "b" comes before "c"). To sort numbers correctly, pass a compare function: it should return a negative number if a comes first, a positive number if b comes first, and 0 if they’re equal.
nums.sort((a, b) => a - b); // ascending
console.log(nums); // [1, 3, 5, 10, 20]
nums.sort((a, b) => b - a); // descending
console.log(nums); // [20, 10, 5, 3, 1]
sort mutates the original and defaults to string order
Two gotchas in one method. First, sort changes the original array, not a copy — if you need to keep the original, sort a copy: [...nums].sort(...). Second, the default comparison is alphabetical, so numbers sort wrong unless you pass a compare function. The (a, b) => a - b pattern for ascending numbers is worth memorizing — it’s one of the most frequently forgotten details in JavaScript.
A few more worth knowing
You won’t reach for these daily, but they round out the toolbox:
const a = [1, 2, 3];
const b = [4, 5];
// concat / spread — join arrays into a new one
console.log(a.concat(b)); // [1, 2, 3, 4, 5]
console.log([...a, ...b]); // [1, 2, 3, 4, 5] (modern equivalent)
// slice — copy a section (does NOT mutate)
console.log(a.slice(0, 2)); // [1, 2]
// join — turn an array into a string
console.log(a.join("-")); // "1-2-3"
// reverse — flip order (DOES mutate, like sort)
console.log([1, 2, 3].reverse()); // [3, 2, 1]
// flat — flatten nested arrays one level
console.log([1, [2, 3], [4]].flat()); // [1, 2, 3, 4]
A quick note on slice versus splice, since the names look almost identical: slice copies a portion and leaves the original alone, while splice mutates by removing or inserting items in place. The lookalike names cause real bugs, so it’s worth keeping them straight.
Chaining methods together
Because map, filter, and friends each return a new array, you can chain them — line up several steps that read top to bottom like a description of what you want:
const orders = [
{ item: "Keyboard", price: 250000, paid: true },
{ item: "Mouse", price: 120000, paid: false },
{ item: "Monitor", price: 1500000, paid: true },
];
const paidTotal = orders
.filter((o) => o.paid) // keep only paid orders
.map((o) => o.price) // pull out the prices
.reduce((sum, p) => sum + p, 0); // add them up
console.log(paidTotal); // 1750000
Read it line by line: keep the paid orders, take their prices, sum them. That readability is the whole reason these methods exist. A for loop could do the same job, but it would mix all three steps together and be harder to follow at a glance.
Chaining is clear, but mind the cost on huge arrays
Method chaining is wonderfully readable and is the right default for everyday lists. Just be aware that each step creates a new array and walks the data again, so on very large datasets (think hundreds of thousands of items in a tight loop) a single for or reduce pass can be more efficient. For the size of data most front-end code touches, the clarity of chaining is well worth it — optimize only when you’ve measured an actual problem.
Mutating vs non-mutating: the key distinction
The single most important thing to keep straight is which methods change the original array and which return a new one. Mixing this up is behind a huge share of array bugs.
- Return a new array, leave the original alone:
map,filter,slice,concat,flat, and reading methods likefind,some,every,includes,indexOf,reduce,join. - Mutate the original in place:
push,pop,shift,unshift,sort,reverse,splice.
When you’re not sure, default to the non-mutating versions — they’re easier to reason about because nothing changes behind your back. If you need to sort or reverse without disturbing the source, make a copy first with the spread operator: [...arr].sort().
Wrapping up
You now have the array methods that power most real JavaScript:
forEachruns a callback on each item and returns nothing — use it for side effects.maptransforms every item into a new array of the same length.filterkeeps only items that pass a test, returning a new (possibly shorter) array.reduceboils an array down to a single value using an accumulator — always pass a start value.find/findIndexreturn the first matching item or its index;some/everyanswer yes/no questions;includes/indexOfcheck for a known value.sortmutates and defaults to string order — pass(a, b) => a - bfor numbers.- Know which methods mutate (
push,pop,sort,reverse,splice) versus return a new array (map,filter,slice,concat), and chain the non-mutating ones for clean, readable pipelines.
Master map, filter, and reduce first — together they handle the overwhelming majority of list work you’ll ever do. The rest slot neatly into place once those three click. Up next, we’ll dig deeper into objects, the other half of how JavaScript holds structured data.