Searching arrays
Find items and positions by exact value or by a callback condition with indexOf, includes, find, findLast, some, and every.
- 01Search by valueUse
indexOf,lastIndexOf, andincludesto find an exact value. Avoid the bugs withNaN, objects, and index 0. - 02Search by testUse
find,findIndex,findLast, andfindLastIndexwhen you know a rule, not the exact value. - 03Ask yes-or-no questionsUse
someandevery. Explain when they stop early and what they give for an empty array.
What searching an array means
An array is a list of values in order. Each value sits at a numbered position called an index. The first index is 0, the next is 1, and so on.
Searching an array means asking the list a question. There are two kinds of question:
- Search by value. You already know the exact value, like the string
"draft". You ask: "Is it in the list? At which index?" - Search by test. You don't know the exact value. You only know a rule, like "the first product that costs more than 20". You ask: "Which item passes this test?"
JavaScript has a method for each kind of question. Some read the list from left to right. Some read it from right to left. Some stop as soon as they know the answer.
const cart = ["tea", "bread", "tea"];
console.log(cart.includes("bread"));
console.log(cart.indexOf("tea"));Line by line:
- Line 1 makes a short list called
cart. It holds"tea","bread", and"tea"again. - Line 2 asks a yes-or-no question: is
"bread"in the list? It is, so it printstrue. - Line 3 asks for a position: where is
"tea"? It prints0, the index of the first"tea".
indexOf the exact value to look for. It compares that value with each item, starting at index 0. The first item that is the same value wins, so the answer is 0.Picture a shelf of books. If you know the exact title, you read the titles from the left. Then you say "It is in slot 3" or "It is not on the shelf." Sometimes you only need to say yes or no. But if someone asks for "the first book written after 2010", a title is no help. You must check each book against that rule.
- In real life: Read the titles from left to right
- In JavaScript:
indexOfchecks indexes from 0 upward - In real life: Say the slot number, or “not here”
- In JavaScript: An index such as
2, or-1for not found - In real life: Only answer “Is it there?”
- In JavaScript:
includesreturnstrueorfalse - In real life: Ask for the first book written after 2010
- In JavaScript:
findruns a test on each item
This lesson uses ideas from earlier lessons:
- Arrays and indexes.
- The strict equality check
===, from Comparisons. - How objects are shared, from Objects & references.
- Functions you pass to other functions, from Function expressions & callbacks and Arrow functions.
Each idea is explained again when it shows up.
indexOf, lastIndexOf & includes: search by value
EXACT VALUEUse these three methods when you already know the exact value you want. You pass the value in. The method compares it with each item in the list.
array.indexOf(value)gives the first index where the value is, or-1if it is not there.array.lastIndexOf(value)gives the last index where the value is, or-1.array.includes(value)givestrueif the value is there, andfalseif not.
Why -1 for "not found"? Because 0 is a real index: it means "the first item". No item can sit at index -1, so it is safe to use as "not found".
const codes = ["A1", "B2", "A1", NaN];const firstA1 = codes.indexOf("A1");const lastA1 = codes.lastIndexOf("A1");const hasNaN = codes.includes(NaN);const nanIndex = codes.indexOf(NaN); const saved = { id: 1 };const objects = [saved, { id: 1 }];const sameReference = objects.indexOf(saved);const newObject = objects.indexOf({ id: 1 }); console.log(firstA1, lastA1, hasNaN, nanIndex);console.log(sameReference, newObject);Lines 1 to 5 search a list of codes:
- Line 1 makes the list.
"A1"is at index 0 and again at index 2. The last item isNaN. - Line 2:
indexOf("A1")finds the first"A1", at index 0. - Line 3:
lastIndexOf("A1")finds the last"A1", at index 2. - Line 4:
includes(NaN)givestrue. - Line 5:
indexOf(NaN)gives-1, even thoughNaNis in the list.
Lines 4 and 5 need an explanation. NaN means "Not a Number". It is the special number you get from a failed math step, like 0 / 0. NaN is the one value where NaN === NaN is false.
indexOf compares items with ===, so it never finds NaN. includes uses a slightly different rule called SameValueZero. It works like ===, except that it treats NaN as equal to NaN. So includes(NaN) finds it.
Lines 7 to 10 search a list of objects:
- Line 7 makes one object and keeps it in the variable
saved. - Line 8 makes a list. It holds
saved, then a second object that only looks the same. - Line 9 searches for
saved. That exact object is at index 0, so the answer is0. - Line 10 searches for a brand-new
{ id: 1 }. It is a third object, not in the list, so the answer is-1.
Each pair of curly braces { } makes a new object. An exact search asks "Is this the very same object?", not "Does it look the same?". That is the rule from Objects & references.
const statuses = ["draft", "sent", "draft"];
console.log(statuses.indexOf("draft", 1));
console.log(statuses.includes("draft", 2));All three methods take an optional second argument. It is called fromIndex: the index where the search starts.
- Line 1 makes a list:
"draft"at index 0,"sent"at index 1,"draft"at index 2. - Line 2 starts at index 1, so it skips the first
"draft". The next one is at index 2, so it prints2. - Line 3 starts at index 2. The search includes index 2 itself, so it prints
true.
You can pass a start index to all three methods: names.indexOf("Ada", 2), names.lastIndexOf("Ada", 3), and names.includes("Ada", 2).
- A positive number skips the items before it.
- A negative number counts back from the end first. For example,
-1means the last index. - The array itself does not change.
Strings have methods with the same names, such as "banana".indexOf("na") and "banana".includes("na"). This lesson is only about the array methods. The String methods lesson covers searching text.
find & findIndex: search by test
STEP THROUGHReal lists often hold objects, and you rarely know the exact object you want. You know a rule instead. For example:
- the first product that is out of stock,
- the first bill that costs more than 100,
- the first user with no profile picture.
For these, you write the rule as a small function. You pass that function to the search method. A function you pass to another function is called a callback. The method calls it for you, once per item, and hands it the item. Your callback returns true for "this one matches" or false for "keep looking".
find(callback)gives the first item that passes the test, orundefinedif none does.findIndex(callback)gives the index of the first item that passes, or-1.
The callback does not have to return exactly true. Any truthy value counts as a pass. Truthy means a value that if treats as true, like 1 or "yes".
You don't tell the librarian a title. You tell her a rule. She picks up the books one by one, in order. The moment one fits your rule, she hands it to you and stops looking.
- In real life: “Show me the first book written after 2010”
- In JavaScript:
books.find(book => book.year > 2010) - In real life: The librarian checks one book at a time
- In JavaScript: The callback gets one item at a time
- In real life: She hands you the first book that fits
- In JavaScript:
findreturns the item itself
const products = [
{ name: "Tea", inStock: true },
{ name: "Tent", inStock: false },
];
const firstOut = products.find((product) => !product.inStock);
console.log(firstOut.name);- Lines 1 to 4 make a list of two products. Tea is in stock. Tent is not.
- Line 5 calls
findwith a callback. The callback gets one product and returns!product.inStock. The!flips the value, so the test is true when the product is not in stock. - Tea fails the test. Tent passes, so
findreturns the Tent object and keeps it infirstOut. - Line 6 prints
firstOut.name, which isTent.
Once Tent passes, find stops. It does not check any later products. This is called stopping early (programmers also say short-circuiting).
find runs your test on one product at a time, from the left. Tea fails, Tent passes, and the search stops there. Torch is never tested. find returns the Tent object itself.Now step through a recording of a real run. Pick a test, then press Step. Watch two things:
- The callback gets one product at a time.
findstops as soon as one product passes.
Guess which product is the first match. Then step through the real callback calls and see where find stops.
script
{ name: "Tea", price: 8, inStock: true }, { name: "Tent", price: 55, inStock: false }, { name: "Torch", price: 24, inStock: true },]; const match = products.find((product) => { return product.price > 20;}); console.log(match?.name);const products = [ { name: "Tea", price: 8, inStock: true }, { name: "Tent", price: 55, inStock: false }, { name: "Torch", price: 24, inStock: true }, { name: "Notebook", price: 12, inStock: false },];products.find((product) => product.price > 20);find returned Tent. It called the callback 2 times, for: Tea → Tent.
filter tests every product and returns all matches. find stops at the first match.The lab also has filter, so you can compare. find returns the first match only. filter checks every item and returns all the matches in a new array. The next lesson, map, filter & reduce, teaches filter fully.
findLast & findLastIndex: search from the end
ES2023Sometimes the answer you want is near the end of the list. For example:
- the newest error in a log,
- the most recent unpaid bill,
- the last message from one person.
findLast and findLastIndex take the same kind of callback as find. The only difference is the direction: they start at the last item and move left.
const logs = ["ok", "error", "ok", "error"];
const latestError = logs.findLast((entry) => entry === "error");
const latestErrorIndex = logs.findLastIndex((entry) => entry === "error");
console.log(latestError, latestErrorIndex);- Line 1 makes a log list.
"error"is at index 1 and at index 3. - Line 2:
findLaststarts at index 3. That item is"error", so it returns"error". - Line 3:
findLastIndexruns the same test from the same end and returns the index,3. - Line 4 prints both answers:
error 3.
findLast starts at the last item and moves left. The "error" at index 3 is the first one it meets, so findLast returns "error" and findLastIndex returns 3.const logs = ["info", "error", "warn", "error"];const lastError = logs.findLast((entry) => entry === "error");const lastErrorIndex = logs.findLastIndex((entry) => entry === "error"); console.log(lastError);console.log(lastErrorIndex);console.log(typeof Array.prototype.findLast === "function");The last error is "error" at index 3. The feature check in this browser gives true.
findLast and findLastIndex were added in ES2023. All modern browsers have them, and a one-line check tells you for sure.These two methods were added in ES2023, the 2023 edition of JavaScript. All modern browsers have them. If your site must work in older browsers, check first with a feature check: typeof Array.prototype.findLast === "function". Array.prototype is the shared object that every array gets its methods from. If the check is false, the browser does not have the method. Your project can then add it with a polyfill: a small piece of code that adds a missing method to older browsers.
some & every: yes-or-no questions
BOOLEANSome questions don't need the item or its index. They only need yes or no. A yes-or-no value is called a boolean: true or false.
some(callback)asks: "Does at least one item pass?" It returnstrueat the first pass.every(callback)asks: "Do all items pass?" It returnsfalseat the first fail.
A teacher reads the attendance list. To answer "Is anyone absent?", she can stop at the first absent student. To answer "Is everyone here?", she must check every name. She can stop early only if she finds someone missing.
- In real life: Is anyone absent?
- In JavaScript:
students.some(student => student.absent) - In real life: Is everyone here?
- In JavaScript:
students.every(student => student.present) - In real life: Stop reading once you know the answer
- In JavaScript: Both methods stop early
const scores = [72, 55, 90];
const hasFailingScore = scores.some((score) => score < 60);
const allAtLeastFifty = scores.every((score) => score >= 50);
console.log(hasFailingScore, allAtLeastFifty);- Line 1 makes a list of three scores: 72, 55, and 90.
- Line 2 asks: is at least one score below 60? 55 is, so
hasFailingScoreistrue. - Line 3 asks: is every score 50 or more? All three are, so
allAtLeastFiftyistrue. - Line 4 prints
true true.
Both answers are true, but the two methods asked different questions.
some stops at the first yes. every must hear yes from every score; one no would make it false.const scores = [84, 91, 77];const hasPerfect = scores.some((score) => score === 100);const allPassing = scores.every((score) => score >= 60); const none = [];console.log(hasPerfect);console.log(allPassing);console.log(none.some(Boolean));console.log(none.every(Boolean));some asks "at least one?" and every asks "all?"Is any score 100? false. Is every score 60 or more? true. On an empty array, some gives false and every gives true.
some stops at the first item that passes. every stops at the first item that fails.The empty list gives answers that look odd at first:
[].some(Boolean)isfalse.someneeds one item that passes, and an empty list has none.[].every(Boolean)istrue.everyonly saysfalsewhen it finds an item that fails, and an empty list has none.
(Boolean here is the built-in function that turns any value into true or false. It is used as the callback.) Mathematicians call the second rule vacuous truth: "all items pass" is true when there are no items to fail.
Which method answers this question?
INTERACTIVERead each row as one method. The columns say what you get back, when to use it, and when it stops.
| Method | What you get back | When to use it | When does it stop? |
|---|---|---|---|
indexOf(value) | The first index, or -1. | You know the exact value and need its position. | At the first match, from the left. |
lastIndexOf(value) | The last index, or -1. | You know the exact value and need its last position. | At the first match, from the right. |
includes(value) | true or false. | You know the exact value and only need yes or no. It also finds NaN. | At the first match, from the left. |
find(callback) | The item, or undefined. | You need the first item that passes a test. | At the first item that passes. |
findIndex(callback) | The index, or -1. | You need the position of the first item that passes. | At the first item that passes. |
findLast(callback) | The item, or undefined. | You need the last item that passes, like the newest error. | At the first pass, starting from the end. |
findLastIndex(callback) | The index, or -1. | You need the position of the last item that passes. | At the first pass, starting from the end. |
some(callback) | true or false. | You ask: does at least one item pass? | At the first item that passes. |
every(callback) | true or false. | You ask: do all items pass? | At the first item that fails. |
Small habits that prevent bugs
- The callback gets three things: the current item, its index, and the whole array. If you need the position, use
findIndex. Don't find the item and then search again for its index. - Search methods only read the array. They don't sort it, remove items, or replace items. Keep your callback a simple test that changes nothing.
- Make values match before an exact search. For example, if stored statuses are lowercase, turn the text a user typed into lowercase before you search.
- Plan for "not found".
findgivesundefined, and the index methods give-1. Reading a property ofundefinedthrows an error. So check the result first, or use optional chaining:match?.namegivesundefinedinstead of an error whenmatchisundefined. - The callback's answer only needs to be truthy, but a clear comparison is easier to read. Write
item.quantity > 1, which is plainly true or false. - Each search reads the array from one end, one item at a time. If your code checks the same long list for exact values many times, a
Setmay be better. ASetis a collection of unique values whosehas()check doesn't read every item.
Before you type a method name, decide what kind of answer you need: an exact value's position, a first match, a last match, or just yes or no. Sort the cards below by that answer.
- Does
roleshold the exact stringadmin? - Give me the first product with
price > 100. - Where is the first product with
price > 100? - Show the newest log entry whose
leveliserror. - At which index is the newest
errorlog? - Is any product out of stock?
- Did every score pass?
- Where is the exact string
draftinstatuses?
Put each question in the group for the kind of answer it needs. Each card's explanation names a good method.
Where you'll use this
You'll search arrays whenever a page holds a list of real data. For example:
- A shopping cart checks whether an item is already in it before adding another row.
- A settings form finds the first field that is filled in wrong.
- A dashboard shows the newest error from a log.
- A permission check asks whether a user has one needed role, or all needed roles.
const cart = [
{ name: "Tea", quantity: 1 },
{ name: "Notebook", quantity: 2 },
];
const hasNotebook = cart.some((item) => item.name === "Notebook");
const firstMany = cart.find((item) => item.quantity > 1);
console.log(hasNotebook, firstMany.name);- Lines 1 to 4 make a cart with one Tea and two Notebooks.
- Line 5 uses
someto ask: is there a Notebook in the cart? Yes, sohasNotebookistrue. - Line 6 uses
findto get the first item with a quantity above 1. That is the Notebook item. - Line 7 prints
true Notebook.
Neither method changes the cart. That makes them safe to use while a page is being drawn on screen.
Start the variable name with the question you are asking:
hasorisgoes well withsome, likehasNotebook.allgoes well withevery, likeallPassing.firstgoes well withfind, likefirstOut.lastgoes well withfindLast, likelastError.
All the methods in this lesson read the array and give back an answer. Later lessons cover methods that build new arrays, sort arrays, and copy arrays safely.
Common misconceptions
"I can use indexOf as a yes-or-no check."
No. indexOf returns a number, not true or false. Inside an if, the number 0 counts as false and -1 counts as true. So if (arr.indexOf(x)) is wrong both ways: it skips an item found at index 0, and it runs when the item is missing. Use includes, or compare with !== -1.
"find returns all the matches."
No. find returns only the first matching item. Use filter when you need every match. The next lesson teaches filter.
"Two objects that look the same are equal."
No. An exact search asks whether it is the very same object. Search for the object you saved, or use find(obj => obj.id === 1) when you mean "the object whose id is 1".
"every on an empty array should be false."
It is true. every only says false when it finds an item that fails the test. An empty array has no items, so nothing fails.
"findLast needs a different kind of callback."
No. It takes the same kind of callback as find. Only the direction changes: it starts at the last index and moves left.
Practice: search arrays on purpose
5 EXERCISESPredict both printed lines before you run the code.
const values = [NaN, "NaN", NaN];
console.log(values.indexOf(NaN));
console.log(values.includes(NaN));The first line prints -1, because indexOf can't find NaN. The second prints true, because includes can. This is one good reason to use includes for yes-or-no checks.
Which product name does this program print?
const products = [
{ name: "Tea", inStock: true },
{ name: "Tent", inStock: false },
{ name: "Torch", inStock: true },
];
const match = products.find((product) => !product.inStock);
console.log(match.name);const products = [
{ name: "Tea", inStock: true },
{ name: "Tent", inStock: false },
{ name: "Torch", inStock: true },
];
const match = products.find((product) => !product.inStock);
console.log(match.name);find tests Tea first. Tea is in stock, so it fails. Then it tests Tent. Tent is out of stock, so it passes, and the program prints Tent.
Which boolean does the program print?
const scores = [72, 88, 61];
console.log(scores.every((score) => score >= 60));const scores = [72, 88, 61];
console.log(scores.every((score) => score >= 60));72, 88, and 61 are all 60 or more. No score fails, so every returns true.
Which message does this program print?
const logs = [
{ level: "info", message: "ready" },
{ level: "error", message: "timeout" },
{ level: "error", message: "retry failed" },
];
const latest = logs.findLast((entry) => entry.level === "error");
console.log(latest.message);const logs = [
{ level: "info", message: "ready" },
{ level: "error", message: "timeout" },
{ level: "error", message: "retry failed" },
];
const latest = logs.findLast((entry) => entry.level === "error");
console.log(latest.message);findLast starts at the end. The last entry is an error, so it matches first. Its message is retry failed.
This code should print allowed, because "admin" is in the list at index 0. But it prints nothing. Write a condition that works.
const roles = ["admin", "editor"];
if (roles.indexOf("admin")) {
console.log("allowed");
}const roles = ["admin", "editor"];
if (roles.indexOf("admin") !== -1) {
console.log("allowed");
}The fixed condition checks the number properly: any index except -1 means found. includes is even clearer when you only need yes or no.
Quiz: check your understanding
7 QUESTIONSFor each question, first ask: does this method give back an index, an item, or a boolean? Try an answer before you read the explanations. Many wrong answers are real bugs people write.
Question 1 of 7What do indexOf and lastIndexOf print?
Read the code, then predictconst letters = ["a", "b", "a"]; console.log(letters.indexOf("a")); console.log(letters.lastIndexOf("a"));Choose an answer to see the explanation.
Question 2 of 7Which method can tell you whether an array holds
NaN?Choose an answer to see the explanation.
Question 3 of 7What does this
findcall print?Read the code, then predictconst products = [{ price: 10 }, { price: 30 }, { price: 50 }]; console.log(products.find((product) => product.price > 20).price);Choose an answer to see the explanation.
Question 4 of 7Which method gives
-1when no item passes the test?Choose an answer to see the explanation.
Question 5 of 7What do
findLastandfindLastIndexprint?Read the code, then predictconst logs = ["error", "warn", "error"]; console.log(logs.findLast((level) => level === "error")); console.log(logs.findLastIndex((level) => level === "error"));Choose an answer to see the explanation.
Question 6 of 7What do
someandeveryprint for an empty array?Read the code, then predictconsole.log([].some(Boolean)); console.log([].every(Boolean));Choose an answer to see the explanation.
Question 7 of 7What is the bug in
if (items.indexOf("apple"))?Choose an answer to see the explanation.
Key takeaways
indexOfandlastIndexOfgive an index, or-1if the value is not there.includesgivestrueorfalse.includes(NaN)findsNaN.indexOf(NaN)never does.- An exact search for an object looks for that very object. To match by a property, like an id, use a callback method.
findandfindIndexstop at the first item that passes the test.findLastandfindLastIndexdo the same, starting from the end.someasks "at least one?" andeveryasks "all?". Both stop as soon as they know the answer.- Never write
if (arr.indexOf(x)). Useincludes, or compare the index with-1.
Remember the one-liner.
Search by value with indexOf or includes, search by test with find, and ask yes or no with some or every.
Up next: map, filter & reduce.