The JavaScript Array find() method searches an array with a callback function and returns the first element that satisfies the condition. If no element matches, it returns undefined. Because it stops at the first match, find() is a useful choice for locating one number, record, or object in a list without changing the original array. This guide expands on the existing syntax and examples with practical callback patterns, safe result handling, and comparisons with related search methods.
Syntax and Formal Specification
The method signature is find(callbackFn, thisArg).
const numbers = [5, 12, 8, 130, 44]; const firstBig = numbers.find(n => n > 10); console.log(firstBig); // 12
Parameters
-
callbackFn— A function invoked for each element. It receives three arguments:-
element— The current element being processed. -
index— The integer index of the current element. -
array— The array on whichfind()was called.
-
-
thisArg(optional) — A value used asthisinside the callback when the callback is a non-arrow function. Ignored for arrow functions, which lexically bindthis.
Return Value
-
The first element for which the callback returns a truthy value.
-
undefinedif no element satisfies the predicate.
console.log([1, 2, 3].find(x => x > 5)); // undefined console.log([1, 2, 3].find(x => x > 1)); // 2 console.log([].find(x => true)); // undefined
Note on Mutation Inside the Callback
find() reads the array length before the search begins and then checks indexes within that initial length. It reads each value when its index is reached, so changes or deletions to later indexes can affect what the callback sees there. Items appended beyond the original length are not visited. Avoid mutating the array inside the predicate so the search works with a consistent list.
// Anti-pattern: do not mutate during find() const arr = [1, 2, 3, 4]; arr.find(n => { if (n === 2) arr.push(99); // pushes index 4 beyond the initial length, so it is not visited return n === 3; });
Performance Characteristics
find() runs in O(n) time worst case, but short-circuits at the first match, so its effective cost depends on where the first match appears. For an array where the target appears near the start, find() completes quickly; for arrays where no match exists, it scans every element.
const big = new Array(1_000_000).fill({ active: false }); big[0] = { active: true }; big[999_999] = { active: true }; const t1 = performance.now(); big.find(x => x.active); console.log(performance.now() - t1); // near-instant, first match const t2 = performance.now(); big.find(x => x.active && x === null); // no match console.log(performance.now() - t2); // full scan
find() vs filter()[0]
filter()[0] allocates an intermediate array of all matches before selecting the first, wasting memory and time. find() short-circuits and avoids the intermediate array entirely. For single-record lookups, always prefer find().
const users = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, { id: 3, name: "Cara" } ]; // Wasteful const first = users.filter(u => u.name.startsWith("B"))[0]; // Idiomatic const firstIdiomatic = users.find(u => u.name.startsWith("B")); console.log(firstIdiomatic); // { id: 2, name: "Bob" }
Benchmarks typically show find() outperforming filter()[0] by a factor of 2–10×, depending on array size and match position.
Sparse Arrays and Holes
find() invokes its callback for every index in the array’s initial length, including empty slots. A hole is read as undefined, so a predicate that accepts every value matches a hole at index 0. The returned value can still be undefined, just as it is when no item matches. Use findIndex() when you need to distinguish an index from no match. If you must tell a hole from an explicitly stored undefined, also check whether the index is an own property.
const holes = new Array(3);
console.log(holes.findIndex(() => true)); // 0: matches the first hole
console.log(holes.findIndex(() => false)); // -1: no match
console.log([undefined].findIndex(() => true)); // 0
find() with Async Predicates
find() does not support async predicates. The callback returns a Promise, which is always truthy, so the first element is always returned—not the first resolved match. Use a manual async loop instead:
async function findAsync(arr, predicate) { for (let i = 0; i < arr.length; i++) { if (await predicate(arr[i], i, arr)) return arr[i]; } return undefined; } // Usage: // const match = await findAsync(items, async item => await checkAsync(item));
Understand the callback arguments
The callback receives the current element, its index, and the original array, in that order. You can use only the arguments you need. The method checks items from the beginning and stops as soon as the callback returns a truthy result.
const names = ["Mira", "Noor", "Sam"];
const firstS = names.find((name, index, array) => {
console.log(`Checking ${index + 1} of ${array.length}`);
return name.startsWith("S");
});
console.log(firstS); // "Sam"
The callback runs for Mira, Noor, and Sam; after Sam matches, later elements would not be checked. Keep the predicate focused on the condition that identifies the element you need.
Find an object by its properties
Arrays often contain objects, such as products, users, or settings. Use a callback to test the property or combination of properties that matters. The first object that meets the condition is returned.
const products = [
{ sku: "A1", price: 120, inStock: false },
{ sku: "B2", price: 80, inStock: true },
{ sku: "C3", price: 50, inStock: true }
];
const availableUnder100 = products.find(
product => product.inStock && product.price < 100
);
console.log(availableUnder100);
// { sku: "B2", price: 80, inStock: true }
find() returns the original object reference from the array; it does not make a copy. If you change a property on the returned object, that change is visible through the original array as well. If you only need a new object, copy it deliberately before changing it.
Write a predicate that returns a result
A predicate should return a value that indicates whether the current element qualifies. A comparison such as item.price < 100 naturally returns a Boolean. In a concise arrow function, that expression is returned automatically.
const numbers = [3, 7, 12, 18];
const firstEven = numbers.find(number => number % 2 === 0);
console.log(firstEven); // 12
When you use a block body with braces, include an explicit return. Without it, the callback returns undefined each time and no item will match.
const firstLongWord = ["cat", "tiger", "horse"].find(word => {
return word.length > 4;
});
console.log(firstLongWord); // "tiger"
The callback does not have to return the Boolean true itself; any truthy value counts as a match. Still, a direct condition is usually easiest to read and avoids accidental matches from values such as nonempty strings or objects.
Handle a missing match safely
If no element passes the predicate, the result is undefined. Test for that before reading a property from the result. Optional chaining is useful when a fallback value is appropriate.
const users = [{ id: 1, name: "Asha" }];
const user = users.find(item => item.id === 5);
console.log(user); // undefined
console.log(user?.name); // undefined
if (user !== undefined) {
console.log(user.name);
}
If an array can contain undefined as a real value, the returned value alone cannot tell you whether that value matched or no match occurred. Use findIndex() when you need to distinguish the cases: it returns an index for a match, including index 0, and -1 when nothing matches.
Use the found element without changing the list
The search itself does not modify the array, but the value it returns may be an object stored in that array. Reading from the result is safe; editing a returned object can mutate data in the source list because both references point to the same object.
const settings = [{ name: "theme", value: "light" }];
const theme = settings.find(item => item.name === "theme");
if (theme) {
theme.value = "dark";
}
console.log(settings[0].value); // "dark"
If you want to preserve the original data, create a copy before changing it. For example, const updatedTheme = { ...theme, value: "dark" } makes a shallow copy of the object, while leaving settings[0] untouched. This distinction matters in state-driven interfaces and any code that relies on comparing old and new data.
