Array methods: searching, sorting, and the async loop trap
find, some and every; comparators that actually sort; non-mutating copies; and why forEach does not wait for async callbacks.
After this lesson you can
- Pick find, findIndex, some, every or includes for a search
- Write a comparator for a multi-key sort and explain why it relies on stability
- Explain why forEach with an async callback finishes before the work does
Lesson 3 covered map, filter and reduce. The rest of the array API
is mostly about asking questions of an array without writing the loop.
Searching
| You want | Use | Returns when nothing matches |
|---|---|---|
| the first matching element | find | undefined |
| its position | findIndex | -1 |
| the last match | findLast, findLastIndex | undefined, -1 |
| whether any element matches | some | false |
| whether every element matches | every | true (for an empty array) |
| whether a value is present | includes | false |
some and every stop at the first element that settles the answer.
every on an empty array is true — there is no element that fails —
which is logically right and still catches people out in validation
code. includes uses SameValueZero, so it finds NaN; indexOf uses
=== and never does.
Sorting with a comparator
A comparator (a, b) => number returns a negative number if a
goes first, positive if b goes first, and 0 if they tie. A
comparator that returns a boolean (a > b) never returns a negative
number, so it is wrong, sometimes in ways that only show on certain
inputs. Strings compare with </> or localeCompare; numbers with
subtraction.
sort has been stable since ES2019: elements that compare equal
keep their original relative order. That is what makes a two-key sort
work in one pass — compare the main key, and only on a tie compare the
second.
sort, reverse and splice change the array in place. toSorted,
toReversed, toSpliced and with(index, value) return a changed copy
and leave the original alone.
export function run() { const scores = [7, 12, 3, 12, 9]; const people = [ { name: "Zura", age: 30 }, { name: "Ana", age: 25 }, { name: "Gio", age: 30 }, ]; const byAge = people.toSorted((a, b) => a.age - b.age); return { firstOverTen: scores.find((n) => n > 10), lastIndexOf12: scores.findLastIndex((n) => n === 12), anyNegative: scores.some((n) => n < 0), everyOnEmpty: [].every((n) => n > 100), includesNaN: [NaN].includes(NaN), indexOfNaN: [NaN].indexOf(NaN), booleanComparator: [3, 1, 2].sort((a, b) => a > b), numericComparator: [3, 1, 2].sort((a, b) => a - b), byAgeStable: byAge.map((p) => p.name), originalUntouched: people.map((p) => p.name), };}forEach does not wait
forEach calls your callback and ignores what it returns. An async
callback returns a promise, so forEach moves straight on: the loop
"finishes" while every callback is still waiting. It also cannot be
stopped early with break or return.
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export async function run() { const log = []; [1, 2].forEach(async (n) => { await wait(10); log.push(`forEach item ${n}`); }); log.push("after forEach"); for (const n of [1, 2]) { await wait(10); log.push(`for-of item ${n}`); } log.push("after for-of"); return log;}for...of with await inside runs the steps one after another. To run
them concurrently and still wait for all of them, use
await Promise.all(items.map(async (x) => ...)) (lesson 5).
Common mistakes
- A boolean comparator.
sort((a, b) => a.score > b.score)returnstrue/false, never a negative number; useb.score - a.score. - Forgetting the tie-breaker. Equal scores stay in input order, not alphabetical order, unless the comparator says so.
asynccallbacks inforEach. The code after the loop runs before any of them finish, as the second example shows.everyon an empty list as a permission check. It istrue, so "every selected item is allowed" passes when nothing is selected.
What interviewers ask
- What does
[1, 2, 3].sort(() => 0)do, and what does it tell you? — Leaves the order unchanged:sortis stable, so ties keep their order. - How do you sort by two keys? — One comparator that compares the first key and falls through to the second only when the first gives ( does this neatly, because is falsy).
Exercise · Pro
A leaderboard with a tie-breaker
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade