Knowledge Base/concepts/JavaScript Array Methods

JavaScript Array Methods

JavaScript array methods are not a prize for avoiding loops. They are a vocabulary for saying what kind of result you want from an array. Use map when every item becomes another item. Use filter when some items survive. Use reduce when the whole array becomes one value. Reach for the wrong one and the code still runs, which is exactly why the bugs can feel so slippery.

If you want the short rule: choose the method by the shape of the output, not by which method looks clever.

The naive approach

Beginners often learn forEach first, then use it for everything:

const prices = [10, 20, 30];
const discounted = [];

prices.forEach((price) => {
  discounted.push(price * 0.9);
});

This works, but the intent is hidden. The code is really saying, "make a new array where every price is discounted." That is exactly what map already means:

const prices = [10, 20, 30];
const discounted = prices.map((price) => price * 0.9);

The result is the same. The second version gives the reader a cleaner promise: the output has one transformed item for every input item.

Choose by output shape

Most array-method decisions can be made from this table:

NeedMethodOutput shape
Transform every itemmapNew array, same length
Keep matching itemsfilterNew array, same or shorter length
Find one itemfindOne value or undefined
Check whether any item passessomeBoolean
Check whether all items passeveryBoolean
Collapse everything into one resultreduceAny single value
Do an action for each itemforEachundefined

That output shape is the contract. If you use map and ignore the returned array, you probably wanted forEach. If you use forEach and push into another array, you probably wanted map or filter. The forEach vs map guide goes deeper on that specific split.

map transforms every element

map runs a callback for each item and puts each return value into a new array.

const users = [
  { id: 1, name: "Ada" },
  { id: 2, name: "Grace" },
  { id: 3, name: "Katherine" },
];

const labels = users.map((user) => `${user.id}: ${user.name}`);

console.log(labels);
// ["1: Ada", "2: Grace", "3: Katherine"]

The original array is untouched. The objects inside it may still be shared references, so do not mutate them inside map unless you genuinely intend to mutate the original data.

const updated = users.map((user) => ({
  ...user,
  selected: false,
}));

That object spread creates a new object for each user. For the shallow-copy caveat, see Spread Operator.

filter keeps matching elements

filter expects the callback to return a truthy or falsy value. Items with a truthy result stay in the new array.

const tasks = [
  { title: "Ship lesson", done: true },
  { title: "Fix typo", done: false },
  { title: "Record walkthrough", done: true },
];

const completed = tasks.filter((task) => task.done);

console.log(completed);
// [{ title: "Ship lesson", done: true }, { title: "Record walkthrough", done: true }]

The callback should be a predicate: one item in, yes-or-no answer out. If your callback returns transformed data, you are probably mixing filter and map.

const namesOfCompletedTasks = tasks
  .filter((task) => task.done)
  .map((task) => task.title);

That chain reads in order: keep completed tasks, then extract their titles.

reduce collapses the array

reduce takes an accumulator and updates it for each item. It can produce a number, string, object, array, Map, or whatever else your code needs.

const cart = [
  { name: "Course", price: 39 },
  { name: "Workbook", price: 12 },
  { name: "Exam pack", price: 19 },
];

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

console.log(total); // 70

Always give reduce an initial value. Without it, JavaScript uses the first array item as the accumulator, which is a lovely way to get a bug shaped like a question mark.

const empty = [];

empty.reduce((sum, n) => sum + n);
// TypeError: Reduce of empty array with no initial value

empty.reduce((sum, n) => sum + n, 0);
// 0

Use reduce when the output really is one accumulated result:

const votes = ["yes", "no", "yes", "yes", "no"];

const counts = votes.reduce((result, vote) => {
  result[vote] = (result[vote] ?? 0) + 1;
  return result;
}, {});

console.log(counts);
// { yes: 3, no: 2 }

If a reduce takes more than a few seconds to understand, split it into named steps. Clever reductions age badly.

find, some, and every avoid noisy filters

You do not need filter when you only care about one item.

const users = [
  { id: 1, role: "student" },
  { id: 2, role: "admin" },
];

const admin = users.find((user) => user.role === "admin");

find stops when it finds the first match. filter always keeps scanning because it has to build the full list of matches.

Use some for "does at least one item pass?"

const hasAdmin = users.some((user) => user.role === "admin");

Use every for "do all items pass?"

const allHaveIds = users.every((user) => typeof user.id === "number");

These methods communicate intent and can stop early. That is useful for both readability and work avoided.

Chaining is allowed, but it is not a personality

Chains are best when each step has a clean job:

const totalForActiveUsers = users
  .filter((user) => user.active)
  .map((user) => user.balance)
  .reduce((sum, balance) => sum + balance, 0);

That is readable because each method matches the output shape: keep active users, extract balances, sum balances.

This is less helpful:

const result = users
  .map((user) => ({ ...user, label: user.name.trim().toLowerCase() }))
  .filter((user) => user.label)
  .map((user) => user.label.replace(/\s+/g, "-"))
  .filter((label) => label.length > 2)
  .reduce((acc, label) => `${acc},${label}`, "");

At that point, name the intermediate steps or write a small helper. Dense code is not advanced code. It is just expensive to read.

When a loop is better

Array methods are not always the clearest answer. Use a for...of loop when you need break, continue, sequential await, or several pieces of state changing together.

for (const user of users) {
  if (user.disabled) continue;

  if (user.role === "admin") {
    sendAdminAlert(user);
    break;
  }
}

The for...of loop exists for this kind of control flow. Do not fight the language to make everything a chain.

Key Takeaways

  • Pick the method by output shape: transformed array, filtered array, one value, boolean, or side effect.
  • map, filter, and reduce return new values, but they do not deep-copy nested objects for you.
  • Give reduce an initial value unless you enjoy empty-array surprises.
  • find, some, and every are often clearer than filtering and then checking the result.
  • A plain for...of loop is the right tool when control flow matters more than pipeline style.
Share this article:
javascriptarraysfunctional