Searching and Testing
Mental model: Ask for the element with find, for its position with findIndex, and for a yes or no answer with some and every.
Level: beginner · about 11 minutes
const users = [
{ id: 1, name: 'ada', admin: false },
{ id: 2, name: 'grace', admin: true },
];
console.log(users.find((u) => u.admin)); // { id: 2, name: 'grace', admin: true }
console.log(users.findIndex((u) => u.admin)); // 1
console.log(users.some((u) => u.admin)); // true
console.log(users.filter((u) => u.admin)); // [ { id: 2, ... } ], an array of oneSame question, four different answers.
Pick the method that matches the question you are actually asking. filter always hands you an array, so users.filter(fn)[0] works but says the wrong thing and keeps scanning after it has an answer. find stops at the first match and hands you the element.
| Method | Takes | Returns | Not found |
|---|---|---|---|
find(fn) | a predicate | the first matching element | undefined |
findIndex(fn) | a predicate | its index | -1 |
findLast(fn) | a predicate | the last matching element | undefined |
findLastIndex(fn) | a predicate | its index | -1 |
includes(v) | a value | true or false | false |
indexOf(v) | a value | its index | -1 |
some(fn) | a predicate | true if any match | false |
every(fn) | a predicate | true if all match | true on an empty array |
Searching from the end
const readings = [12, 40, 8, 40, 3];
console.log(readings.findIndex((n) => n === 40)); // 1, first from the left
console.log(readings.findLastIndex((n) => n === 40)); // 3, first from the right
console.log(readings.findLast((n) => n > 10)); // 40
console.log(readings.lastIndexOf(40)); // 3, value search, not predicate
some and every: one boolean, short-circuited
const scores = [70, 85, 90];
console.log(scores.some((n) => n < 75)); // true, stops at 70
console.log(scores.every((n) => n >= 70)); // true, has to check all three
console.log([].some((n) => true)); // false, nothing to find
console.log([].every((n) => false)); // true, "all zero of them passed"
The NaN search caveat
const values = [1, NaN, 3];
console.log(values.indexOf(NaN)); // -1, cannot find it
console.log(values.includes(NaN)); // true, finds it
console.log(NaN === NaN); // false, the reason indexOf failsThe same array, two search methods, two different answers.
indexOf compares with strict equality, and NaN === NaN is false, so NaN is invisible to it. includes uses SameValueZero, which treats NaN as equal to itself while still treating 0 and -0 as the same. Object.is is stricter again: it separates 0 from -0.
console.log([0].includes(-0)); // true, SameValueZero
console.log([0].indexOf(-0)); // 0, strict equality agrees here
console.log(Object.is(0, -0)); // false, the strictest comparison
console.log([1, NaN].findIndex((v) => Object.is(v, NaN))); // 1, search NaN by index
is it there?includeswhere is it?indexOffor a value,findIndexfor a conditiongive me the objectfindany of them?someall of them?everysearching for `NaN`?includes, neverindexOf
const ids = [0, 5, 9];
if (ids.indexOf(0)) console.log('found 0');
else console.log('did not find 0');indexOf(0) returns the index 0, which is falsy, so the else branch runs even though the value is present. Every truthiness check on an index is a bug waiting for index 0. Use ids.includes(0).
Try it yourself
Ask the right question
const logins = [
{ user: 'ada', ok: true },
{ user: 'alan', ok: false },
{ user: 'grace', ok: true },
{ user: 'alan', ok: false },
];
console.log(logins.find((l) => !l.ok)); // first failure
console.log(logins.findIndex((l) => !l.ok)); // where it was
console.log(logins.some((l) => !l.ok)); // any failures at all?
console.log(logins.every((l) => l.user.length > 2)); // all names long enough?
console.log(logins.map((l) => l.user).includes('grace')); // is grace here?
Add a findLast that gets the most recent failed login. Then rewrite the some call as a find and compare what you get back.
Exercises
An indexOf that can find NaN
Write strictIndexOf(arr, value) that returns the index of the first element that is the same value as value, or -1 if there is none. It must find NaN, and it must treat 0 and -0 as different.
Check yourself
- What does this log?
- -1 true —
indexOfuses strict equality andNaN === NaNisfalse, so it reports-1.includesuses SameValueZero, which does considerNaNequal to itself, so it returnstrue. - You need the user object whose id is 42, from an array of thousands. Which is best?
users.find((u) => u.id === 42)—findstops at the first match and returns the element itself. Thefilterversion scans the whole array and then throws away all but one result.includesandindexOfcompare against whole elements, not against a property.- What does
[].every((x) => x > 100)return? true— There is no element that fails the test, so the claim holds trivially. If "no items" should be a failure in your code, test the length separately rather than expectingeveryto do it.
Common mistakes
- Using an index result as a boolean.
-1is truthy and0is falsy. - Reaching for
filter(...)[0]when you meantfind. - Trying to locate
NaNwithindexOf, which always reports-1.
Takeaways
findreturns the element,findIndexthe position,some/everya boolean.findLastandfindLastIndexsearch from the right without reversing the array.includesuses SameValueZero, so it findsNaN;indexOfuses===, so it cannot.everyon an empty array istrue, andsomeon an empty array isfalse.