Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~18 min· topic 3 of 10

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 indexOf and findIndex return 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 includes uses: like ===, except NaN equals NaN.
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.

app.jswhole filejavascript
const tags = ["js", "css", "html"];
tags.includes("css"); // true
tags.indexOf("css");  // 1
tags.indexOf("php");  // -1

02Check -1 correctly

if (tags.indexOf("js")) is a bug: "js" is at index 0, and 0 is falsy. Compare with -1, or use includes.

app.jswhole filejavascript
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.

Search by a conditiondiagram
Rendering diagram…

04Yes-or-no about the whole list

some asks "is there at least one?". every asks "is there no exception?". Both stop early.

app.jswhole filejavascript
const ages = [31, 17, 15];
ages.some((a) => a < 18);   // true
ages.every((a) => a >= 18); // false

05Search from the end

findLast is handy for "the most recent" when items are stored oldest first, without reversing the array.

app.jswhole filejavascript
const events = [{ type: "save" }, { type: "error" }, { type: "save" }];
events.findLastIndex((e) => e.type === "save"); // 2

Try it yourself

  1. 1

    Find by name

    In the second example, use find to get the user whose name is "Meera" and log their age. Then predict what happens if you search for "meera" (small m).

  2. 2

    Make every fail early

    In the third example, change the every test to n > 1. Predict allSmall and seen before running.

  3. 3

    Guard against empty

    Write const allPassed = scores.length > 0 && scores.every((s) => s >= 40); with const scores = [];. Why is the length check needed?

Code & diagrams

indexOf, lastIndexOf and includes ES2016+ New tab

`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, findLast, some and every ES2023+ New tab

`find`/`findIndex` are ES2015; `findLast`/`findLastIndex` are ES2023. Look at the last line: on an empty array, `every` is `true`.

Preview

Press Run to see the page.

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 stops as soon as it knows New tab

`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"];.

terminal
$ Console
── what you'll see ──
(nothing is printed)

Break #2

Reading a property of a find that found nothing

Write users.find((u) => u.name === "Zoya").age when no Zoya exists.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot read properties of undefined (reading 'age')

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 a Map keyed by id (for lookup) once, then each lookup is O(1) on average.

  • ▸

    indexOf/lastIndexOf skip holes; includes, find and findIndex visit holes and see undefined. So [,].includes(undefined) is true but [,].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.

  • ▸

    find and friends are generic: they work on array-like objects through Array.prototype.find.call(obj, test). Typed arrays have their own versions of all of them.

Remember this

  1. 1

    indexOf(value) returns the index of the first element equal to value, or **-1** if there is none. lastIndexOf searches 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 match 5. Always check !== -1, never truthiness: an item found at index 0 gives 0, which is falsy.

  2. 2

    includes(value) (ES2016) returns true or false. It's clearer than indexOf(x) !== -1 when you only need yes or no. One difference: includes uses the SameValueZero comparison, which treats NaN as equal to NaN, so [NaN].includes(NaN) is true while [NaN].indexOf(NaN) is -1.

  3. 3

    For objects, "equal" means the same object, not "an object that looks the same". [{ id: 1 }].includes({ id: 1 }) is false, because the two { id: 1 } are different objects. To search by content, use a test function.

  4. 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, or undefined. findIndex(test) returns that element's index, or -1. findLast and findLastIndex (ES2023) do the same starting from the end. The callback receives (element, index, array).

  5. 5

    some(test) returns true if at least one element passes; every(test) returns true if all pass. All of find, findIndex, some and every stop early (short-circuit) as soon as they know the answer. For an empty array, some is false and every is true (there's no element that fails), which surprises people and can let empty data slip through a check.

  6. 6

    Which to use: an exact primitive value? includes (yes/no) or indexOf (position). A condition? find (the item), findIndex (the position), some/every (yes/no). Need all matches, not just the first? That's filter, in Topic 5.4. Searching a big array many times? Build a Set or Map once and look up in it (Phase 9), or keep the array sorted and use binary search (/dsa).

Explain it without notes

01

What is the difference between indexOf and includes, including how they handle NaN?

02

When would you use find instead of indexOf?

03

What do some and every return for an empty array, and why?

04

Why does [{ a: 1 }].includes({ a: 1 }) return false?

Practice

01

Given const words = ["apple", "Banana", "cherry"];, print whether any word starts with a capital letter, and the index of the first such word.

02

Write hasDuplicate(arr) that returns true if any value appears twice, using indexOf and some.

Trade-offs

  • ↔

    includes/indexOf are simple and fast for small arrays; for many lookups in a big array, building a Set or Map once costs memory but makes each lookup O(1).

  • ↔

    find with a predicate is flexible and readable; a plain for loop with break does 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 indexOf results against -1, not truthiness.

  • I can find the first (or last) item matching a condition with find/findLast.

  • I can use some and every and know what they return for an empty array.

  • I know objects are compared by reference when searching.