Array Methods — map, filter, find, forEach

9 min read

A loop says "for each thing, do this." That's a useful sentence, but most test code wants to say something more specific: "give me a new list where each item is transformed," "filter the list to just the failures," "find me the admin." JavaScript's array methods say those sentences directly. This lesson covers the five you'll reach for every day, the chain pattern that combines them, and the rule that tells them apart.

Why array methods matter

A for-loop processes an array. An array method describes what you want from the array — and lets the language do the bookkeeping. Two snippets that compute the same thing:

// Manual loop
const failedNamesA = [];
for (let i = 0; i < results.length; i++) {
  if (results[i].status === "fail") {
    failedNamesA.push(results[i].name);
  }
}
 
// With array methods
const failedNamesB = results
  .filter(r => r.status === "fail")
  .map(r => r.name);

The second version is shorter, has no off-by-one risk, and reads as English. Once you're fluent with the methods, you'll write the loop maybe once a year.

forEach — do something with each item

forEach walks the array, calling your function once per item. It returns nothing useful — its job is the side effect.

const tests = ["login", "checkout", "search"];
tests.forEach(name => console.log(`Running ${name}`));

Output:

Running login
Running checkout
Running search

Use forEach for genuinely side-effecty work — printing, calling an API for each item, recording metrics. If you need a result — a new array, a count, a found item — pick a different method below.

map — transform each item

map runs your function over every item and returns a new array of the same length, with each item replaced by whatever your function returned.

const users = [
  { name: "Alice", role: "admin" },
  { name: "Bob",   role: "member" },
  { name: "Carol", role: "guest" }
];
 
const names = users.map(u => u.name);
console.log(names);  // ["Alice", "Bob", "Carol"]

The original users array is untouched. map is for the "transform each item" pattern — extracting a field, formatting a string, computing a derived value.

filter — keep matching items

filter runs your function on every item and returns a new array of just the items where your function returned a truthy value.

const results = [
  { name: "login",    status: "pass" },
  { name: "checkout", status: "fail" },
  { name: "search",   status: "pass" },
  { name: "logout",   status: "fail" }
];
 
const failures = results.filter(r => r.status === "fail");
console.log(failures);

Output:

[
  { name: 'checkout', status: 'fail' },
  { name: 'logout',   status: 'fail' }
]

The function passed to filter is sometimes called a predicate — its job is to return true or false for each item. Items where the predicate is true survive; others are dropped.

find — get the first match

find returns the first item where your function returns true, or undefined if nothing matched. Unlike filter, it returns one item, not an array.

const users = [
  { name: "Alice", role: "admin" },
  { name: "Bob",   role: "member" },
  { name: "Carol", role: "admin" }
];
 
const firstAdmin = users.find(u => u.role === "admin");
console.log(firstAdmin);   // { name: 'Alice', role: 'admin' }
 
const banana = users.find(u => u.name === "banana");
console.log(banana);       // undefined

find short-circuits — it stops walking the array the moment it finds a match.

some and every — boolean over a whole array

Two more methods round out the set. some returns true if at least one item matches; every returns true if all items match.

const results = [
  { name: "login",    status: "pass" },
  { name: "checkout", status: "fail" }
];
 
console.log(results.some(r => r.status === "fail"));   // true  — at least one failed
console.log(results.every(r => r.status === "pass"));  // false — not all passed

some is perfect for "are there any failures?" assertions. every is perfect for "did everything pass?" gates.

The differences at a glance

  • forEach → returns undefined. Side-effect only.
  • map → returns a new array, same length, items transformed.
  • filter → returns a new array, possibly shorter, items unchanged.
  • find → returns one item or undefined.
  • some/every → return true or false.

Pick the method whose return value matches what you actually want. If you want one item, use find, not filter(...)[0]. If you want a yes/no, use some or every, not filter(...).length > 0.

Chaining

Each method returns something — map returns an array, filter returns an array, and you can call another method on the result. That's how a complex transformation reads as a sentence:

const results = [
  { name: "login",    status: "pass", durationMs: 124 },
  { name: "checkout", status: "fail", durationMs: 890 },
  { name: "search",   status: "fail", durationMs: 2350 },
  { name: "logout",   status: "pass", durationMs: 67 }
];
 
const slowFailures = results
  .filter(r => r.status === "fail")           // narrow to failures
  .filter(r => r.durationMs > 1000)           // narrow to slow ones
  .map(r => `${r.name} (${r.durationMs}ms)`); // format each
 
console.log(slowFailures);

Output:

[ 'search (2350ms)' ]

Read top-to-bottom: take the results, keep failures, keep slow ones, format. That's exactly the kind of pipeline you'll write to surface "the most concerning N items" in a test report.

Data flow through a chain

Step 1 of 5

Raw test results

4 items: 2 passes, 2 failures (one slow, one fast).

Each method is a small, predictable transform. The chain is a pipeline of those transforms. Once you can see it that way, complex test data processing becomes routine.

A real example — counting failures by severity

A common test report shape — categorise failures by severity, print a summary.

const failures = [
  { name: "login",    severity: "P0" },
  { name: "checkout", severity: "P0" },
  { name: "search",   severity: "P1" },
  { name: "logout",   severity: "P2" }
];
 
const p0 = failures.filter(f => f.severity === "P0").length;
const p1 = failures.filter(f => f.severity === "P1").length;
const p2 = failures.filter(f => f.severity === "P2").length;
 
console.log(`P0: ${p0}, P1: ${p1}, P2: ${p2}`);

Output:

P0: 2, P1: 1, P2: 1

Three filter calls, one per severity. (You'll meet reduce in more advanced JS — it can do this in one pass — but three filters is perfectly readable, and clarity beats cleverness in test code.)

⚠️ Common mistakes

  • Forgetting that forEach doesn't return anything. const x = arr.forEach(...) makes x always undefined. If you need a result, use map, filter, or find.
  • Confusing filter and find. filter always returns an array (possibly empty); find returns one item or undefined. Asserting on arr.filter(...)[0] works, but it's a sign you should be using find instead.
  • Mutating items inside map. Your callback should return a new value, not mutate the existing item. users.map(u => { u.role = "guest"; return u; }) mutates the original objects — surprising every caller. Return a new object instead: users.map(u => ({ ...u, role: "guest" })) (spread is two lessons away).

🎯 Practice task

Build a test result analyser. 20-25 minutes.

  1. In your js-for-qa folder, create analyse.js.
  2. Declare an array of 6 test results with name, status ("pass" or "fail"), durationMs, and severity ("P0", "P1", or "P2").
  3. Use forEach to print each result on its own line.
  4. Use filter to make const failures = results.filter(...).
  5. Use map on failures to make const failureNames = failures.map(...). Print it.
  6. Use find to get the first P0 failure, if any. Print it.
  7. Use some to print true/false for "any failures at all?"
  8. Use every to print true/false for "did all tests pass?"
  9. Run with node analyse.js and confirm the output reflects your data.
  10. Stretch: chain everything into one expression — const slowestFailureName = results.filter(r => r.status === "fail").sort((a, b) => b.durationMs - a.durationMs)[0]?.name;. Print the result. (sort mutates by default; [...arr].sort(...) if you want a copy.)

The next lesson moves from arrays to objects — the structure each item in those arrays has been all along.

// tip to track lessons you complete and pick up where you left off across devices.