Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 6 – Arrays and array methods

Searching: includes, indexOf, find, some and every

Search JavaScript arrays by value with includes and indexOf, by condition with find, some and every, from the end with findLast, and know how NaN is handled.

  • Beginner
  • 14 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 14 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Pick the right search method for value vs condition searches
  • Search from the end with findLast and findLastIndex
  • Explain NaN handling differences between search methods

Before you start

What you’ll learn · Before you start

What you will learn

  • Pick the right search method for value vs condition searches
  • Search from the end with findLast and findLastIndex
  • Explain NaN handling differences between search methods

Before you start

On this page

JavaScript arrays have two families of search methods. To look for a value, includes(value) answers true or false, and indexOf(value) gives the index of the first match, or -1. To look for an item that meets a condition, you pass a function that returns true or false for an item: find(fn) returns the first item that passes, or undefined; findIndex(fn) returns that item’s index, or -1; some(fn) asks whether any item passes and every(fn) whether all of them do. findLast() and findLastIndex() search from the end. Every one of them stops as soon as it knows the answer. This lesson runs each method, shows where they disagree (NaN, objects and empty arrays) and measures when an array is the wrong place to search at all.

Searching for a value: includes and indexOf

Ticket numbers on a waiting list JavaScript · value-search.mjs
// Searching for a known value: is it there (includes), and at which index (indexOf, lastIndexOf)?
const waitlist = [104, 87, 23, 56, 23];

console.log('includes(23):', waitlist.includes(23), '| includes(99):', waitlist.includes(99));
console.log('indexOf(23):', waitlist.indexOf(23), '| lastIndexOf(23):', waitlist.lastIndexOf(23));
console.log('indexOf(99):', waitlist.indexOf(99));
console.log('indexOf(23, 3):', waitlist.indexOf(23, 3), '| includes(104, 1):', waitlist.includes(104, 1));
console.log("strict: includes('23'):", waitlist.includes('23'), "| indexOf('23'):", waitlist.indexOf('23'));

// The classic trap: index 0 is falsy, and -1 is truthy.
console.log('if (indexOf(104)) would run:', Boolean(waitlist.indexOf(104)));
console.log('if (indexOf(99)) would run:', Boolean(waitlist.indexOf(99)));
console.log('the right test:', waitlist.indexOf(104) !== -1, waitlist.includes(104));

Output

includes(23): true | includes(99): false
indexOf(23): 2 | lastIndexOf(23): 4
indexOf(99): -1
indexOf(23, 3): 4 | includes(104, 1): false
strict: includes('23'): false | indexOf('23'): -1
if (indexOf(104)) would run: false
if (indexOf(99)) would run: true
the right test: true true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node value-search.mjs

includes(23) only says whether 23 is there. indexOf(23) says where: index 2, the first of the two matches, and lastIndexOf(23) searches from the end and finds the other one at index 4. When the value is missing, indexOf() and lastIndexOf() return -1, never undefined. A second argument tells the search where to start, so indexOf(23, 3) skips the first match and includes(104, 1) misses the 104 at index 0. Both methods compare without converting types: the string '23' is not the number 23, so neither of them finds it.

The last three lines show the classic mistake with indexOf(). Its result is a position, and position 0 is a falsy number, so if (waitlist.indexOf(104)) skips its block although 104 is right at the front, while -1 is truthy, so if (waitlist.indexOf(99)) runs its block for a number that is not there. When you only need yes or no, use includes(); when you need the position too, compare it with -1.

NaN, -0 and objects

The two value searches compare in different ways, and the difference shows on three kinds of value:

Where includes() and indexOf() disagree JavaScript · nan-search.mjs
// includes() compares with SameValueZero and indexOf() with ===, so they disagree about NaN. Both compare objects by
// reference: a look-alike object is a different object.
const readings = [21.5, NaN, 23.5, -0];
console.log('includes(NaN):', readings.includes(NaN), '| indexOf(NaN):', readings.indexOf(NaN));
console.log('findIndex(Number.isNaN):', readings.findIndex(Number.isNaN));
console.log('includes(0):', readings.includes(0), '| indexOf(0):', readings.indexOf(0));

const asha = { id: 7, name: 'Asha' };
const users = [asha, { id: 9, name: 'Ravi' }];
console.log('the same object:', users.includes(asha), '| a look-alike:', users.includes({ id: 7, name: 'Asha' }));
console.log('by a condition:', users.some((u) => u.id === 7), users.findIndex((u) => u.id === 9));

const withHole = [1, , 3];
console.log('a hole: includes(undefined)', withHole.includes(undefined), '| indexOf(undefined)', withHole.indexOf(undefined));

Output

includes(NaN): true | indexOf(NaN): -1
findIndex(Number.isNaN): 1
includes(0): true | indexOf(0): 3
the same object: true | a look-alike: false
by a condition: true 1
a hole: includes(undefined) true | indexOf(undefined) -1

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node nan-search.mjs

includes() uses the comparison the specification calls SameValueZero: it is ===, except that NaN counts as equal to NaN. indexOf() and lastIndexOf() use plain ===, and NaN === NaN is false, so they can never find a NaN, a common value in sensor readings or in numbers parsed from bad input. To find its position, search by a condition with findIndex(Number.isNaN). Both comparisons treat 0 and -0 as equal.

Objects are compared by reference, as === compares them: users.includes(asha) finds the very object stored in the array, but a new object with the same properties is a different object, so the look-alike is not found. To find a user by their id, search with a condition: some() answers yes or no, and find() or findIndex() locate the match. The last line shows one more difference that the specification notes: includes() reads a hole as undefined, while indexOf() skips holes, so they also disagree about undefined in a sparse array.

Searching by a condition: find and findIndex

find() calls your function with each item in turn, from index 0, and returns the first item for which it returns a truthy value. findIndex() does the same and returns the index instead:

Finding trains in a day's departures JavaScript · train-search.mjs
// Departures from one platform, in time order. Times are 'HH:MM' strings with leading zeros, so comparing them as
// strings compares them as times.
const departures = [
  { train: 'Intercity', at: '06:10', seats: 0 },
  { train: 'Passenger', at: '09:45', seats: 120 },
  { train: 'Express', at: '13:30', seats: 0 },
  { train: 'Superfast', at: '18:05', seats: 14 },
  { train: 'Mail', at: '21:40', seats: 0 },
  { train: 'Night Express', at: '23:55', seats: 3 },
];

const leavesAfter = (time) => (d) => d.at > time;

console.log('first after 10:00:', departures.find(leavesAfter('10:00')));
console.log('its index:', departures.findIndex(leavesAfter('10:00')));
console.log('first after 10:00 with seats:', departures.find((d) => d.at > '10:00' && d.seats > 0).train);
console.log('last of the day with seats:', departures.findLast((d) => d.seats > 0).train);
console.log('index of the last full train:', departures.findLastIndex((d) => d.seats === 0));
console.log('first after 23:59:', departures.find(leavesAfter('23:59')), departures.findIndex(leavesAfter('23:59')));

Output

first after 10:00: { train: 'Express', at: '13:30', seats: 0 }
its index: 2
first after 10:00 with seats: Superfast
last of the day with seats: Night Express
index of the last full train: 4
first after 23:59: undefined -1

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node train-search.mjs

The first departure after 10:00 is the Express at index 2, although it has no seats; adding d.seats > 0 to the condition skips it and finds the Superfast. leavesAfter('10:00') is a function that returns the test function, a pattern from the functions module that keeps conditions readable. The times work as strings only because every one has two digits for the hour and two for the minutes, so '09:45' < '13:30' compares them character by character in time order.

find() returns the item itself, not a copy: changing the object it returns changes the object in the array. When nothing passes, it returns undefined and findIndex() returns -1. If the array can hold undefined values themselves, find()’s answer is ambiguous, so use findIndex() and compare with -1. The function also receives the index and the whole array as its second and third arguments, as the next lesson’s methods do.

Searching from the end: findLast and findLastIndex

findLast() and findLastIndex(), added in ECMAScript 2023, work like find() and findIndex() but start at the last index and move towards the front. In the timetable, findLast((d) => d.seats > 0) gives the last train of the day that still has seats, the Night Express, and findLastIndex((d) => d.seats === 0) gives 4, the index of the Mail, the last full train. Use them whenever “the most recent” or “the last” is the question: the latest payment that failed, the last attempt that passed. The older way, [...list].reverse().find(fn), copies the whole array first, and its findIndex() version counts positions in the reversed copy.

Version note

findLast() and findLastIndex() need Node.js 18 or later, Chrome 97, Firefox 104 or Safari 15.4.

Yes or no: some and every

some(fn) returns true as soon as one item passes, and every(fn) returns false as soon as one item fails. Both stop there, which the call counts show:

some() and every(), on four marks and on none JavaScript · every-empty.mjs
// some() stops at the first true answer and every() at the first false one. On an empty array, some() is false and
// every() is true.
const marks = [72, 35, 91, 48];
let calls = 0;
const failed = (mark) => {
  calls++;
  return mark < 40;
};

console.log('some failed?', marks.some(failed), 'after', calls, 'calls');
calls = 0;
const passed = (mark) => {
  calls++;
  return mark >= 40;
};
console.log('every passed?', marks.every(passed), 'after', calls, 'calls');

const noMarks = [];
console.log('empty: some failed?', noMarks.some(failed), '| every passed?', noMarks.every(passed));
console.log('"all passed" that needs at least one mark:', noMarks.length > 0 && noMarks.every(passed));

Output

some failed? true after 2 calls
every passed? false after 2 calls
empty: some failed? false | every passed? true
"all passed" that needs at least one mark: false

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node every-empty.mjs

The second mark, 35, is below 40, so some(failed) stopped after 2 calls, and every(passed) stopped at the same mark. The surprise comes with an empty array: some() returns false, because no item passed, and every() returns true, because no item failed. The specification says it directly: every acts like the “for all” of mathematics, and for an empty array it returns true. That is logical, and it is a bug whenever an empty list means that something went wrong, such as a list of checks that failed to load. Test the length as well, as the last line does, when “nothing to check” must not count as success.

Diagram: search for a value with includes or indexOf; search with a condition using find, findIndex, some or every.What are you searching with?A value to compareA condition: a functionincludes(v)true or falseindexOf(v), lastIndexOf(v)the index, or -1find(fn), findLast(fn)the item, or undefinedfindIndex(fn), findLastIndex(fn)the index, or -1some(fn)does any item pass?every(fn)do all items pass?a valueno exact value?use a condition

Which search method answers which question

Text description of the diagram

The diagram starts with one question: what are you searching with?

  • With a value, which is compared with === or SameValueZero, two boxes follow. includes(v) answers true or false. indexOf(v) and lastIndexOf(v) give the index of the first or the last match, or -1.
  • When there is no exact value to compare, a dashed arrow leads to searches with a condition, a function that returns true or false for an item. find(fn) and findLast(fn) give the first or the last item that passes, or undefined. findIndex(fn) and findLastIndex(fn) give its index, or -1. some(fn) asks whether any item passes, and every(fn) whether all of them do.
Method Returns On an empty array
includes(v) true or false false
indexOf(v), lastIndexOf(v) the index of the first or last match, or -1 -1
find(fn), findLast(fn) the first or last item that passes, or undefined undefined
findIndex(fn), findLastIndex(fn) the index of that item, or -1 -1
some(fn) true when any item passes false
every(fn) true when no item fails true

Many lookups: build a Set

Each of these methods walks the array, item by item, so a value that is not there costs a read of every item. One search is cheap. Thousands of searches against the same long list are not:

2,000 usernames checked against 50,000 taken ones JavaScript · set-lookups.mjs
// 2,000 sign-ups checked against 50,000 usernames already taken. includes() scans the array on every check; a Set
// is built once and then finds a name without scanning. Times are the fastest of three runs, in milliseconds.
const taken = Array.from({ length: 50_000 }, (_, i) => `user${i}`);
const signUps = Array.from({ length: 2_000 }, (_, i) => `user${i * 37}`);

function countTaken(isTaken) {
  let count = 0;
  for (const name of signUps) if (isTaken(name)) count++;
  return count;
}

function fastest(task) {
  let best = Infinity;
  let result;
  for (let run = 0; run < 3; run++) {
    const start = performance.now();
    result = task();
    best = Math.min(best, performance.now() - start);
  }
  return [result, best];
}

const [clashes, scanMs] = fastest(() => countTaken((name) => taken.includes(name)));
const [set, buildMs] = fastest(() => new Set(taken));
const [clashes2, lookupMs] = fastest(() => countTaken((name) => set.has(name)));

console.log(`names already taken: ${clashes} of ${signUps.length} (the Set agrees: ${clashes === clashes2})`);
console.log(`includes() on every sign-up: ${scanMs.toFixed(1).padStart(7)} ms`);
console.log(`new Set(taken), once:        ${buildMs.toFixed(1).padStart(7)} ms`);
console.log(`set.has() on every sign-up:  ${lookupMs.toFixed(2).padStart(7)} ms`);

Output

names already taken: 1352 of 2000 (the Set agrees: true)
includes() on every sign-up:   346.0 ms
new Set(taken), once:            3.3 ms
set.has() on every sign-up:     0.29 ms

This output changes from run to run: the times depend on the computer and on what else it is running

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node set-lookups.mjs

Of the 2,000 sign-ups, 648 picked a name nobody had, and each of those checks read all 50,000 taken names, more than 32 million string comparisons for those alone. A Set stores each value once and finds a value without scanning: the specification requires access times that, on average, grow more slowly than the number of items. Building it is a single pass over the array, and after that every check is a lookup. In the run above, the 2,000 checks took hundreds of times less time with set.has() than with includes(), and the Set still wins by far once the time to build it is added. A Set compares with SameValueZero, like includes(). So: one search, use an array method; many searches against the same list, build a Set once, or a Map from each key to its item, which the collections module covers.

Compare Two Lists Paste two lists, such as sign-ups and taken names, to see which items appear in both. JavaScript & TypeScript Online Compiler Change the marks in every-empty.mjs and watch how many calls some() and every() make before they stop.

Exercises

Exercise · Easy · JavaScript

Find the latest passing attempt and check that every invoice is paid

Write two functions in checks.mjs.

  • latestPassingAttempt(attempts, passMark) gets a learner's attempts at a test in the order they were made, each an object such as { attempt: 3, score: 64 }, and returns the most recent attempt whose score is at least passMark, or undefined when none is. A score that is NaN (the marking failed) never counts as passing.
  • allPaid(invoices) gets invoices such as { id: 'INV-7', status: 'paid' } and returns true when there is at least one invoice and every invoice has the status 'paid'. A customer with no invoices at all gets false: there is nothing to show as paid.

The starter returns the first passing attempt instead of the latest one, and its allPaid() says true for an empty list. Run the sample tests when you are done.

Starter code · checks.mjs

// The checks of lesson javascript/searching-arrays.

export function latestPassingAttempt(attempts, passMark) {
  // Returns the first passing attempt, not the latest one.
  return attempts.find((a) => a.score >= passMark);
}

export function allPaid(invoices) {
  // Says true for a customer with no invoices.
  return invoices.every((invoice) => invoice.status === 'paid');
}
The sample tests · checks.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { latestPassingAttempt, allPaid } from './checks.mjs';

const attempts = [
  { attempt: 1, score: 55 },
  { attempt: 2, score: 72 },
  { attempt: 3, score: 64 },
  { attempt: 4, score: 38 },
];

test('the latest attempt at or above the mark', () => {
  assert.deepEqual(latestPassingAttempt(attempts, 60), { attempt: 3, score: 64 });
  assert.deepEqual(latestPassingAttempt(attempts, 72), { attempt: 2, score: 72 });
});

test('undefined when no attempt passes', () => {
  assert.equal(latestPassingAttempt(attempts, 80), undefined);
  assert.equal(latestPassingAttempt([], 40), undefined);
});

test('a NaN score never passes', () => {
  const marked = [{ attempt: 1, score: 70 }, { attempt: 2, score: NaN }];
  assert.deepEqual(latestPassingAttempt(marked, 50), { attempt: 1, score: 70 });
});

test('every invoice paid', () => {
  assert.equal(allPaid([{ id: 'INV-1', status: 'paid' }, { id: 'INV-2', status: 'paid' }]), true);
});

test('one invoice still due', () => {
  assert.equal(allPaid([{ id: 'INV-1', status: 'paid' }, { id: 'INV-2', status: 'due' }]), false);
});

test('no invoices at all is not "all paid"', () => {
  assert.equal(allPaid([]), false);
});
A hint

find() searches from the front and stops at the first match; another method searches from the back. A comparison with NaN, such as NaN >= 50, is always false, so a plain >= test already rejects those scores. For allPaid(), remember what every() returns for an empty array, and check the length first.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

7 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 2 of 7 What do [NaN].includes(NaN) and [NaN].indexOf(NaN) return?

    Choose one answer.

    Show the answer to question 2

    Answer: true and -1

    includes() compares with SameValueZero, under which NaN equals NaN. indexOf() compares with ===, and NaN === NaN is false, so it can never find NaN. findIndex(Number.isNaN) can.

  2. Question 3 of 7 What does this program print?

    Read the code, then choose one answer.

    const xs = [3, 8, 5, 10];
    
    console.log(xs.find((n) => n > 4), xs.findLast((n) => n > 4), xs.findIndex((n) => n > 20));
    Show the answer to question 3

    Answer: 8 10 -1

    find() returns the first item that passes, 8, and findLast() searches from the end and returns 10. findIndex() returns -1 when no item passes.

  3. Question 4 of 7 What does [].every((mark) => mark >= 40) return?

    Choose one answer.

    Show the answer to question 4

    Answer: true

    every() returns false only when it finds an item that fails, and an empty array has none, so the answer is true. some() on an empty array is false. Check the length first when "no items" must not count as success.

  4. Question 5 of 7 Which methods return the matching item itself, rather than an index or a boolean? Choose every right one.

    Choose every answer that is right.

    Show the answer to question 5

    Answer:

    • find()
    • findLast()

    find() and findLast() return the item, or undefined. findIndex() and indexOf() return an index, or -1, and some() returns true or false.

  5. Question 6 of 7 What does train-search.mjs print?

    What does this program print? Choose one answer.

    // Departures from one platform, in time order. Times are 'HH:MM' strings with leading zeros, so comparing them as
    // strings compares them as times.
    const departures = [
      { train: 'Intercity', at: '06:10', seats: 0 },
      { train: 'Passenger', at: '09:45', seats: 120 },
      { train: 'Express', at: '13:30', seats: 0 },
      { train: 'Superfast', at: '18:05', seats: 14 },
      { train: 'Mail', at: '21:40', seats: 0 },
      { train: 'Night Express', at: '23:55', seats: 3 },
    ];
    
    const leavesAfter = (time) => (d) => d.at > time;
    
    console.log('first after 10:00:', departures.find(leavesAfter('10:00')));
    console.log('its index:', departures.findIndex(leavesAfter('10:00')));
    console.log('first after 10:00 with seats:', departures.find((d) => d.at > '10:00' && d.seats > 0).train);
    console.log('last of the day with seats:', departures.findLast((d) => d.seats > 0).train);
    console.log('index of the last full train:', departures.findLastIndex((d) => d.seats === 0));
    console.log('first after 23:59:', departures.find(leavesAfter('23:59')), departures.findIndex(leavesAfter('23:59')));
    Show the answer to question 6

    Answer: it prints

    first after 10:00: { train: 'Express', at: '13:30', seats: 0 }
    its index: 2
    first after 10:00 with seats: Superfast
    last of the day with seats: Night Express
    index of the last full train: 4
    first after 23:59: undefined -1

    The first departure after 10:00 is the Express at 13:30, even though it has no seats; adding d.seats > 0 to the condition skips it. findLast() and findLastIndex() search from the end, so they find the Night Express and the Mail at index 4. With no match, find() gives undefined and findIndex() gives -1.

  6. Question 7 of 7 A form checks every new username against an array of 50,000 names already taken. Which change helps most when many names are checked?

    Choose one answer.

    Show the answer to question 7

    Answer: Build a Set from the array once, then call has() for each name

    includes(), indexOf() and some() all scan the array, so each name that is not taken reads all 50,000 names. A Set is built once and then finds a name without scanning: in this lesson's measured run, the checks took hundreds of times longer with includes() than with has().

Interview questions

Fresher · What is the difference between includes() and indexOf() on a JavaScript array?

includes(value) returns true or false, while indexOf(value) returns the index of the first match, or -1 when there is none. They also compare differently. includes() uses SameValueZero, so it finds NaN; indexOf() uses ===, so [NaN].indexOf(NaN) is -1. Both treat 0 and -0 as equal, compare objects by reference and never convert types, so [1].includes('1') is false. includes() reads holes as undefined, while indexOf() skips them. Use includes() for yes-or-no checks, because if (list.indexOf(x)) is wrong for a match at index 0, and use indexOf() when you need the position. For many lookups against a large array, build a Set once.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A release script runs const ok = checks.every((c) => c.passed) before deploying. One night the CI configuration failed to load, checks came back as an empty array, and a broken build went to production. Explain why the script said yes, and how you would make it fail closed.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A sign-up endpoint checks each new username against 300,000 reserved and taken names with reserved.includes(name.toLowerCase()). It is fast in tests but slow under load. Estimate the cost, propose a better structure with numbers, and say what changes about how names are compared.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • With a value: includes() answers yes or no, indexOf() and lastIndexOf() give the first or last index, or -1. Never test indexOf() for truthiness: compare it with -1.
  • With a condition: find() and findLast() give the item or undefined; findIndex() and findLastIndex() give its index or -1.
  • includes() finds NaN and indexOf() cannot; use findIndex(Number.isNaN) for its position. Objects are found only by reference, so search for them with a condition.
  • some() stops at the first item that passes and every() at the first that fails. On an empty array some() is false and every() is true.
  • Every array search reads the items one by one. For many lookups against the same list, build a Set once.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.