Topic 5.3
Searching Arrays
In one line
indexOf and includes look for an exact value, find and findIndex look for the first item that passes a test you write (findLast searches from the end), and some and every answer yes-or-no questions about the whole array.
Think of it like this
Looking for your friend in a class photo. "Is Ravi in the photo?" is includes. "Where is Ravi standing?" is indexOf. "Find me the first kid wearing a red cap" is find: you describe what you're looking for instead of naming one exact person. "Is anyone wearing glasses?" is some, and "Is everyone smiling?" is every.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Callback
- A function you hand to another function, which calls it for you, for example once per array item.
- Predicate
- A callback that answers yes or no (returns true or false) about one item.
- -1
- What
indexOfandfindIndexreturn when nothing matches, because no real index is negative. - Strict equality
- The
===comparison: same type and same value, and for objects, the very same object. - SameValueZero
- The comparison
includesuses: like===, exceptNaNequalsNaN. - Short-circuit
- Stopping as soon as the answer is known, without looking at the rest.
Step by step
01Is it there? Where?
For plain values, includes and indexOf are all you need.
const tags = ["js", "css", "html"];
tags.includes("css"); // true
tags.indexOf("css"); // 1
tags.indexOf("php"); // -102Check -1 correctly
if (tags.indexOf("js")) is a bug: "js" is at index 0, and 0 is falsy. Compare with -1, or use includes.
if (tags.indexOf("js") !== -1) { /* found */ }
if (tags.includes("js")) { /* clearer */ }03Search by a condition
Arrays of objects need a test. find runs your predicate on each item until one returns true.
04Yes-or-no about the whole list
some asks "is there at least one?". every asks "is there no exception?". Both stop early.
const ages = [31, 17, 15];
ages.some((a) => a < 18); // true
ages.every((a) => a >= 18); // false05Search from the end
findLast is handy for "the most recent" when items are stored oldest first, without reversing the array.
const events = [{ type: "save" }, { type: "error" }, { type: "save" }];
events.findLastIndex((e) => e.type === "save"); // 2Try it yourself
- 1
Find by name
In the second example, use
findto get the user whosenameis"Meera"and log theirage. Then predict what happens if you search for"meera"(small m). - 2
Make every fail early
In the third example, change the
everytest ton > 1. PredictallSmallandseenbefore running. - 3
Guard against empty
Write
const allPassed = scores.length > 0 && scores.every((s) => s >= 40);withconst scores = [];. Why is thelengthcheck needed?
Code & diagrams
`includes` is ES2016. `"130"` (a string) is not `130` (a number). Objects are only equal to themselves.
Console
(nothing was printed)Expected console output
1
5
5
-1
true
false
-1 true
false`find`/`findIndex` are ES2015; `findLast`/`findLastIndex` are ES2023. Look at the last line: on an empty array, `every` is `true`.
Preview
Console
Nothing logged yet.Expected console output
{ id: 2, name: 'Ravi', age: 17 }
1
{ id: 3, name: 'Meera', age: 15 }
2
undefined
-1
true false
false true`some` stopped at 50 (found one); `every` stopped at 50 too (found an exception). Neither looked at 7 or 90.
Console
(nothing was printed)Expected console output
true [ 3, 50 ]
false [ 3, 50 ]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Using indexOf as a true/false test
Write if (tags.indexOf("js")) { console.log("has js"); } with const tags = ["js", "css"];.
Break #2
Reading a property of a find that found nothing
Write users.find((u) => u.name === "Zoya").age when no Zoya exists.
Myth vs fact
Myth
includes and indexOf compare objects by their contents.
Fact
Both compare object references. Two separate objects with the same properties are not equal. Use find or some with a test that compares the fields you care about.
Myth
find returns all the matching items.
Fact
It returns only the first match (or undefined). filter returns all matches as an array (Topic 5.4).
Myth
every on an empty array returns false.
Fact
It returns true: no element fails the test. This is called "vacuous truth" in logic. Check length too if empty should fail.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
All these searches are linear, O(n): they look at items one by one. Searching an array of 10,000 items inside a loop over 10,000 items is 100 million checks. Convert to a
Set(for membership) or aMapkeyed by id (for lookup) once, then each lookup is O(1) on average. - ▸
indexOf/lastIndexOfskip holes;includes,findandfindIndexvisit holes and seeundefined. So[,].includes(undefined)istruebut[,].indexOf(undefined)is-1. - ▸
Callbacks get
(element, index, array). Passing a function that takes extra optional parameters can bite you: the index lands in the second parameter. This is the same trap as["1","2"].map(parseInt)in Topic 5.4. - ▸
findand friends are generic: they work on array-like objects throughArray.prototype.find.call(obj, test). Typed arrays have their own versions of all of them.
Remember this
- 1
indexOf(value)returns the index of the first element equal tovalue, or **-1** if there is none.lastIndexOfsearches from the end. Both take an optional start position:indexOf(12, 2)starts looking at index 2. They compare with strict equality (===, Phase 2), so"5"doesn't match5. Always check!== -1, never truthiness: an item found at index0gives0, which is falsy. - 2
includes(value)(ES2016) returnstrueorfalse. It's clearer thanindexOf(x) !== -1when you only need yes or no. One difference:includesuses the SameValueZero comparison, which treatsNaNas equal toNaN, so[NaN].includes(NaN)istruewhile[NaN].indexOf(NaN)is-1. - 3
For objects, "equal" means the same object, not "an object that looks the same".
[{ id: 1 }].includes({ id: 1 })isfalse, because the two{ id: 1 }are different objects. To search by content, use a test function. - 4
find(test)(ES2015) calls your callback (a function you pass in, Phase 3) on each element in order, and returns the first element for which it returns a truthy value, orundefined.findIndex(test)returns that element's index, or-1.findLastandfindLastIndex(ES2023) do the same starting from the end. The callback receives(element, index, array). - 5
some(test)returnstrueif at least one element passes;every(test)returnstrueif all pass. All offind,findIndex,someandeverystop early (short-circuit) as soon as they know the answer. For an empty array,someisfalseandeveryistrue(there's no element that fails), which surprises people and can let empty data slip through a check. - 6
Which to use: an exact primitive value?
includes(yes/no) orindexOf(position). A condition?find(the item),findIndex(the position),some/every(yes/no). Need all matches, not just the first? That'sfilter, in Topic 5.4. Searching a big array many times? Build aSetorMaponce and look up in it (Phase 9), or keep the array sorted and use binary search (/dsa).
Explain it without notes
What is the difference between indexOf and includes, including how they handle NaN?
When would you use find instead of indexOf?
What do some and every return for an empty array, and why?
Why does [{ a: 1 }].includes({ a: 1 }) return false?
Practice
Given const words = ["apple", "Banana", "cherry"];, print whether any word starts with a capital letter, and the index of the first such word.
Write hasDuplicate(arr) that returns true if any value appears twice, using indexOf and some.
Trade-offs
- ↔
includes/indexOfare simple and fast for small arrays; for many lookups in a big array, building aSetorMaponce costs memory but makes each lookup O(1). - ↔
findwith a predicate is flexible and readable; a plainforloop withbreakdoes the same job and is sometimes clearer when the search has several steps.
Done when you can
I can check whether a value is in an array with
includes.I check
indexOfresults against-1, not truthiness.I can find the first (or last) item matching a condition with
find/findLast.I can use
someandeveryand know what they return for an empty array.I know objects are compared by reference when searching.