findIndex()
ES6+Returns the index of the first element in the array that satisfies the provided testing function.
Syntax
array.findIndex(callback(element, index, array), thisArg)Parameters
callback Function Function to test each element
Return Value
The index of the first matching element, or -1
Examples
const numbers = [5, 12, 8, 130, 44];
const index = numbers.findIndex(x => x > 10);
console.log(index); 📌 When to Use
Use findIndex() when the position of a match is what you actually need - almost always because you are about to do something positional with it: splice() the element out, replace it in place, insert a new item just before or after it, or slice the array around it. If you only intend to read the element, find() gets you there in one step without the extra arr[index] dereference; if you only need to know whether a match exists, some() returns the boolean directly and never tempts anyone into the index !== -1 dance. For primitive values compared by simple equality, indexOf() does the same job with less ceremony and no callback allocation. A typical state-management scenario makes the distinction concrete: updating one todo in a list requires its index to build the new array - [...todos.slice(0, i), updated, ...todos.slice(i + 1)] - so findIndex() is the right first move, whereas merely displaying that todo would call for find(). When the interesting match is the most recent one in an ordered log, findLastIndex() (ES2023) searches from the tail and avoids scanning irrelevant history.
⚠️ Common Mistakes
Forgetting that a miss returns -1, not undefined or null. -1 is a perfectly valid-looking number that flows silently into arithmetic and indexing: arr[-1] is undefined in JavaScript rather than an error, and splice(-1, 1) actively deletes the LAST element - so an unchecked miss can corrupt data instead of crashing loudly.
Using findIndex(...) !== -1 as an existence test. It works, but it forces every reader to mentally translate index semantics into a boolean, and it invites the classic if (idx) typo. some() answers "does any element match?" directly and reads as the question being asked.
Checking the result with if (index) instead of if (index !== -1). Index 0 is falsy, so a match at the very first position is treated as "not found" - and because the first position is often the most common match location, this bug fires constantly while looking like it works in tests that match later elements.
Deleting multiple matches with a single pass of cached indices. After you splice() at one index, every later index shifts down by one, so a list of positions collected up front becomes stale after the first removal. Either splice from the highest index downward, or sidestep the problem entirely with filter().
Reaching for findIndex() with a callback when comparing primitives: nums.findIndex(n => n === 5) is just nums.indexOf(5) with extra allocation and noise. Reserve findIndex() for predicates that indexOf() cannot express, such as matching an object property or a range condition.
✅ Best Practices
Guard every use of the returned index with an explicit !== -1 check before splicing, slicing, or assigning. The guard is one line; the alternative is splice(-1, ...) quietly mutating the wrong end of the array on the first unexpected miss in production.
Choose by comparison type: indexOf() for strict-equality lookup of primitives, findIndex() for anything requiring a predicate - object properties, case-insensitive matches, numeric ranges. Picking the narrower tool documents what kind of matching is happening without reading the callback.
Pair findIndex() with splice() for in-place removal or replacement - find the position, guard against -1, then mutate once. For frameworks that need immutable updates, pair it with toSpliced() (ES2023) or the slice-and-spread pattern instead, so the index drives construction of a new array.
In TypeScript, prefer find() plus a direct reference over findIndex() plus arr[i] when both would work: the find() result is typed as T | undefined and forces handling of the miss, whereas arr[i] after an unchecked index lookup is typed as plain T even when i is -1.
When updating one element immutably, map() with an id comparison often reads better than findIndex() plus slicing: items.map(it => it.id === id ? { ...it, done: true } : it) expresses "same list, one item changed" in a single expression with no index bookkeeping.
⚡ Performance Notes
findIndex() short-circuits at the first match, so its average cost is half the array length and its worst case (no match) is a full O(n) scan with one callback invocation per element. For primitives, indexOf() is measurably faster in V8 because it performs an internal strict-equality scan with no user-function calls at all - the engine never has to jump between native iteration and JavaScript callback frames. Like find(), findIndex() visits holes in sparse arrays (the callback sees undefined) rather than skipping them, which is a spec-level difference from indexOf(). The scaling concern mirrors every linear search: one findIndex() call is negligible at any realistic size, but findIndex() inside a loop over a second collection multiplies into O(n*m). If your code repeatedly resolves ids to positions - a drag-and-drop list, for instance - build a Map from id to index once after each reorder and look positions up in constant time until the next change.
🌍 Real World Example
Updating an Item in a Shopping Cart
A shopping cart update is the canonical findIndex() job because the operation is positional: the code must modify one specific entry of a mutable list, so it needs to know where that entry lives. The function locates the product by id, guards the -1 miss case explicitly, and reports success or failure to the caller instead of failing silently - the boolean return lets UI code decide whether to show an error toast. If this cart lived in React or Svelte state, the same index would instead drive an immutable rebuild with toSpliced() or slice-and-spread, but the lookup step would be identical.
const cart = [
{productId: 101, name: 'Laptop', quantity: 1},
{productId: 102, name: 'Mouse', quantity: 2}
];
function updateQuantity(productId, newQuantity) {
const index = cart.findIndex(item => item.productId === productId);
if (index !== -1) {
cart[index].quantity = newQuantity;
return true;
}
return false; // Product not found
}
updateQuantity(102, 5); // Mouse quantity now 5