Knowledge Base/concepts/map vs filter vs find: How to Choose the Right Array Method

map vs filter vs find: How to Choose the Right Array Method

The real map vs filter vs find decision is not about which array method feels more JavaScript-y. It is about the shape of the answer your program needs. map makes one new value for every old value. filter keeps the matching values. find returns the first matching value and stops. Pick by result shape first, then write the callback.

That sounds small, but it prevents a lot of code that technically works while saying the wrong thing.

The short rule

Use the method that matches the result you want:

You needUseResult
A transformed item for every input itemmapNew array, same length
All items that pass a testfilterNew array, same or shorter length
The first item that passes a testfindOne item or undefined

The callback should match that job too:

  • map gets a transform: item in, new value out.
  • filter gets a predicate: item in, yes-or-no answer out.
  • find gets a predicate: item in, yes-or-no answer out, but only until the first match.

If you are squinting at the callback to figure out what the method is doing, the code is already making you work too hard.

The naive approach that hides intent

A common beginner move is to reach for filter whenever there is a condition:

const users = [
  { id: 1, name: "Ada", role: "student" },
  { id: 2, name: "Grace", role: "admin" },
  { id: 3, name: "Lin", role: "student" },
];

const admin = users.filter((user) => user.role === "admin")[0];

console.log(admin);
// { id: 2, name: "Grace", role: "admin" }

The output is correct, but the code says, "build me every matching admin, then take the first one." If the rule is really "get the first admin," say that:

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

That version is shorter, but the important part is the promise. find tells the reader there should be one selected record, not a list.

map transforms every item

Use map when the output array should line up with the input array. Same number of items, different shape or value.

const products = [
  { sku: "A1", name: "Keyboard", price: 85 },
  { sku: "B2", name: "Mouse", price: 35 },
  { sku: "C3", name: "Monitor", price: 240 },
];

const labels = products.map((product) => `${product.sku}: ${product.name}`);

console.log(labels);
// ["A1: Keyboard", "B2: Mouse", "C3: Monitor"]

map is not asking whether an item should stay. It assumes every item produces one output item.

That is why this is suspicious:

const expensiveProducts = products.map((product) => {
  if (product.price >= 100) {
    return product;
  }
});

console.log(expensiveProducts);
// [undefined, undefined, { sku: "C3", name: "Monitor", price: 240 }]

The callback forgot to return a value for products under 100. map still keeps the same length, so the missing branches become undefined. If you want to remove items, use filter.

filter keeps matching items

Use filter when you want a list of items that pass a test.

const expensiveProducts = products.filter((product) => product.price >= 100);

console.log(expensiveProducts);
// [{ sku: "C3", name: "Monitor", price: 240 }]

The callback should answer a boolean-style question: should this item stay?

Good filter callbacks often read like business rules:

const visibleCourses = courses.filter((course) => {
  return course.published && !course.archived;
});

If your filter callback starts building strings, objects, or numbers, you are probably doing two jobs. Split them:

const visibleCourseCards = courses
  .filter((course) => course.published && !course.archived)
  .map((course) => ({
    id: course.id,
    title: course.title,
    lessonCount: course.lessons.length,
  }));

Now the sequence is honest: keep the visible courses, then transform each course into a card model.

find returns one matching item

Use find when the caller needs one item, usually because an id, slug, status, or priority rule points to a single useful record.

const invoice = invoices.find((invoice) => invoice.id === selectedInvoiceId);

The result is either the original matching item or undefined.

const nextBlockedStep = steps.find((step) => step.status === "blocked");

if (nextBlockedStep) {
  console.log(nextBlockedStep.title);
}

That undefined result is not a failure of find. It is part of the contract. If the rest of your code wants a different fallback, make that explicit:

const overdueInvoice =
  invoices.find((invoice) => invoice.status === "overdue") ?? null;

Returning null can be useful when an API or component already treats null as "nothing selected." The key is that you choose the fallback at the boundary instead of pretending a missing match cannot happen.

find can stop earlier than filter

find stops after the first match. filter has to inspect the whole array because it needs every match.

const checks = [
  { name: "syntax", failed: false },
  { name: "tests", failed: true },
  { name: "coverage", failed: true },
];

let findChecks = 0;
const firstFailure = checks.find((check) => {
  findChecks += 1;
  return check.failed;
});

let filterChecks = 0;
const allFailures = checks.filter((check) => {
  filterChecks += 1;
  return check.failed;
});

console.log(firstFailure.name); // "tests"
console.log(allFailures.length); // 2
console.log(findChecks); // 2
console.log(filterChecks); // 3

That does not mean find is always "faster" in a useful way. Most arrays in app code are small enough that readability matters more. But the early stop is a real semantic difference: find is for the first successful match, while filter is for the complete set of matches.

The callback decides the meaning

The method sets the result shape. The callback sets the rule.

const names = users.map((user) => user.name);
const admins = users.filter((user) => user.role === "admin");
const currentUser = users.find((user) => user.id === currentUserId);

Those three lines are easy to scan because each callback fits the method:

  • user.name transforms a user into a string.
  • user.role === "admin" decides whether a user stays.
  • user.id === currentUserId decides whether this is the selected user.

This version is harder on the reader:

const result = users
  .map((user) => (user.active ? user.email : null))
  .filter(Boolean)
  .find((email) => email.endsWith("@company.com"));

It works, but the data shape keeps changing: users, then emails or null, then emails, then one email. If that pipeline is part of important code, name the steps:

const activeEmails = users
  .filter((user) => user.active)
  .map((user) => user.email);

const companyEmail = activeEmails.find((email) =>
  email.endsWith("@company.com"),
);

That is a few more lines and much less detective work.

Mutating inside these methods is usually the wrong smell

map, filter, and find do not mutate the array by themselves. Your callback still can.

const updatedUsers = users.map((user) => {
  user.active = false;
  return user;
});

That mutates the original user objects and returns the same object references in a new array. The array is new. The objects are not.

Prefer returning new objects when you are transforming records:

const updatedUsers = users.map((user) => ({
  ...user,
  active: false,
}));

The Spread Operator article covers the shallow-copy part of that pattern. The short version: object spread copies the top level, not every nested object.

When none of the three is right

Use some and every when the answer should be a boolean:

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

Use forEach vs map guidance when you are choosing between side effects and transformations. Use a for...of loop when you need break, continue, sequential await, or several pieces of changing state.

Array methods are good vocabulary. They are not a tax you pay to avoid loops.

Key Takeaways

  • Choose map, filter, or find by result shape before writing the callback.
  • map returns a new array with the same length, so conditional map callbacks often create accidental undefined values.
  • filter returns every match. Do not use filter(...)[0] when the rule is first match.
  • find returns one item or undefined, so handle the missing case deliberately.
  • Split pipelines when the data shape changes more than once or the method chain stops being easy to scan.
Share this article:
javascriptarraysfundamentalsfunctional