JavaScript Array findIndex() Method: Find Element Index by Condition
Learn the JavaScript Array findIndex() method to find the index of the first element that satisfies a condition. Includes syntax, examples, and best practices.
The findIndex()
method returns the index of the first element in an array that satisfies the provided testing function. It's a powerful tool for locating elements based on complex conditions, returning -1 when no element matches.
Understanding Array findIndex()
The findIndex()
method executes a callback function once for each element in the array until it finds one where the callback returns a truthy value. It then returns the index of that element and stops iterating.
Syntax
array.findIndex(callback(element[, index[, array]])[, thisArg])
Parameters
- callback: Function to test each element, taking three arguments:
element
: The current element being processedindex
(optional): The index of the current elementarray
(optional): The array findIndex was called upon
- thisArg (optional): Value to use as
this
when executing the callback
Return Value
- The index of the first element that passes the test (0 or positive integer)
- -1 if no element passes the test
Basic Usage
Finding Simple Values
const numbers = [10, 20, 30, 40, 50];
// Find index of first number greater than 25
const index1 = numbers.findIndex((num) => num > 25);
console.log(index1); // 2 (element 30)
// Find index of first even number
const index2 = numbers.findIndex((num) => num % 2 === 0);
console.log(index2); // 0 (element 10)
// No match returns -1
const index3 = numbers.findIndex((num) => num > 100);
console.log(index3); // -1
Finding Objects in Arrays
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 },
{ id: 4, name: 'David', age: 40 },
];
// Find index by property
const bobIndex = users.findIndex((user) => user.name === 'Bob');
console.log(bobIndex); // 1
// Find index by multiple conditions
const seniorIndex = users.findIndex((user) => user.age >= 35);
console.log(seniorIndex); // 2
// Find index by id
const userIndex = users.findIndex((user) => user.id === 3);
console.log(userIndex); // 2
Using All Callback Parameters
const arr = ['apple', 'banana', 'cherry', 'date'];
// Using element, index, and array parameters
const result = arr.findIndex((element, index, array) => {
console.log(`Checking ${element} at index ${index}`);
return element.length > 5 && index > array.length / 2;
});
console.log(result); // 2 (cherry - length > 5 and index > 2)
Practical Examples
Form Validation
const formFields = [
{ name: 'username', value: 'john_doe', isValid: true },
{ name: 'email', value: 'john@example', isValid: false },
{ name: 'password', value: '12345678', isValid: true },
{ name: 'confirmPassword', value: '12345678', isValid: true },
];
// Find first invalid field
const firstInvalidIndex = formFields.findIndex((field) => !field.isValid);
if (firstInvalidIndex !== -1) {
const invalidField = formFields[firstInvalidIndex];
console.log(`First invalid field: ${invalidField.name}`);
// Focus on this field in the UI
} else {
console.log('All fields are valid');
}
Task Management
class TaskList {
constructor() {
this.tasks = [];
}
addTask(task) {
this.tasks.push({
id: Date.now(),
text: task,
completed: false,
priority: 'normal',
});
}
findTaskIndex(id) {
return this.tasks.findIndex((task) => task.id === id);
}
completeTask(id) {
const index = this.findTaskIndex(id);
if (index !== -1) {
this.tasks[index].completed = true;
return true;
}
return false;
}
removeTask(id) {
const index = this.findTaskIndex(id);
if (index !== -1) {
return this.tasks.splice(index, 1)[0];
}
return null;
}
findFirstIncomplete() {
const index = this.tasks.findIndex((task) => !task.completed);
return index !== -1 ? this.tasks[index] : null;
}
}
const todoList = new TaskList();
todoList.addTask('Buy groceries');
todoList.addTask('Write report');
todoList.addTask('Call client');
console.log(todoList.findFirstIncomplete());
// Returns first incomplete task
Array Updates Based on Condition
function updateFirstMatching(array, predicate, updates) {
const index = array.findIndex(predicate);
if (index !== -1) {
// Update the found element
array[index] = { ...array[index], ...updates };
return true;
}
return false;
}
const products = [
{ id: 1, name: 'Laptop', price: 999, inStock: true },
{ id: 2, name: 'Mouse', price: 29, inStock: false },
{ id: 3, name: 'Keyboard', price: 79, inStock: true },
];
// Update first out-of-stock item
const updated = updateFirstMatching(products, (product) => !product.inStock, {
inStock: true,
restockedAt: new Date(),
});
console.log(updated); // true
console.log(products[1]); // Mouse is now in stock with restockedAt date
Comparison with Other Methods
findIndex() vs indexOf()
const numbers = [1, 2, 3, 4, 5];
const objects = [{ value: 1 }, { value: 2 }, { value: 3 }];
// indexOf() - works with primitive values only
console.log(numbers.indexOf(3)); // 2
console.log(numbers.indexOf(6)); // -1
// findIndex() - works with any condition
console.log(numbers.findIndex((n) => n === 3)); // 2
console.log(numbers.findIndex((n) => n > 3)); // 3
// For objects, indexOf won't work as expected
const obj = { value: 2 };
console.log(objects.indexOf(obj)); // -1 (different object reference)
// But findIndex() works perfectly
console.log(objects.findIndex((o) => o.value === 2)); // 1
findIndex() vs find()
const users = [
{ id: 1, name: 'Alice', active: false },
{ id: 2, name: 'Bob', active: true },
{ id: 3, name: 'Charlie', active: true },
];
// find() returns the element
const activeUser = users.find((user) => user.active);
console.log(activeUser); // { id: 2, name: 'Bob', active: true }
// findIndex() returns the index
const activeUserIndex = users.findIndex((user) => user.active);
console.log(activeUserIndex); // 1
// Use case: When you need both index and element
const index = users.findIndex((user) => user.active);
if (index !== -1) {
const user = users[index];
console.log(`Active user ${user.name} at index ${index}`);
}
Advanced Usage
Using thisArg Parameter
const validator = {
minLength: 5,
maxLength: 10,
findInvalidIndex(strings) {
return strings.findIndex(function (str) {
return str.length < this.minLength || str.length > this.maxLength;
}, this); // Pass 'this' context
},
};
const words = ['Hi', 'Hello', 'JavaScript', 'World'];
const invalidIndex = validator.findInvalidIndex(words);
console.log(invalidIndex); // 0 ('Hi' is too short)
Early Exit Optimization
// findIndex() stops as soon as it finds a match
const expensiveCheck = (item) => {
console.log(`Checking item: ${item}`);
// Simulate expensive operation
return item > 1000;
};
const numbers = [1, 10, 100, 1500, 2000, 3000];
const index = numbers.findIndex(expensiveCheck);
// Only logs: Checking item: 1, 10, 100, 1500
// Stops after finding 1500
console.log(index); // 3
Complex Search Patterns
class SearchEngine {
constructor(data) {
this.data = data;
}
// Find by exact match
findExact(field, value) {
return this.data.findIndex((item) => item[field] === value);
}
// Find by partial match
findPartial(field, value) {
const searchTerm = value.toLowerCase();
return this.data.findIndex((item) =>
item[field].toLowerCase().includes(searchTerm)
);
}
// Find by multiple fields
findByMultipleFields(criteria) {
return this.data.findIndex((item) =>
Object.entries(criteria).every(([key, value]) => item[key] === value)
);
}
// Find by custom predicate
findCustom(predicate) {
return this.data.findIndex(predicate);
}
}
const products = [
{ id: 1, name: 'iPhone 13', category: 'phones', price: 999 },
{ id: 2, name: 'Samsung Galaxy', category: 'phones', price: 899 },
{ id: 3, name: 'iPad Pro', category: 'tablets', price: 1099 },
];
const search = new SearchEngine(products);
console.log(search.findExact('name', 'iPad Pro')); // 2
console.log(search.findPartial('name', 'galaxy')); // 1
console.log(
search.findByMultipleFields({
category: 'phones',
price: 999,
})
); // 0
Working with Sparse Arrays
const sparse = [1, , , 4, , 6];
// findIndex() skips empty slots
const index = sparse.findIndex((value, index) => {
console.log(`Index ${index}: ${value}`);
return value > 3;
});
// Logs: Index 0: 1, Index 3: 4
// Skips indices 1, 2, and 4
console.log(index); // 3
Error Handling
function safeFindIndex(array, predicate, errorValue = -1) {
try {
if (!Array.isArray(array)) {
throw new TypeError('First argument must be an array');
}
if (typeof predicate !== 'function') {
throw new TypeError('Second argument must be a function');
}
return array.findIndex(predicate);
} catch (error) {
console.error('Error in safeFindIndex:', error.message);
return errorValue;
}
}
// Usage
console.log(safeFindIndex([1, 2, 3], (n) => n > 2)); // 2
console.log(safeFindIndex(null, (n) => n > 2)); // -1 (with error log)
console.log(safeFindIndex([1, 2, 3], 'not a function')); // -1 (with error log)
Performance Tips
// 1. Exit early when possible
const users = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `User${i}`,
active: i % 100 === 0,
}));
// Efficient: stops at first match
console.time('findIndex');
const activeIndex = users.findIndex((user) => user.active);
console.timeEnd('findIndex');
// 2. Avoid unnecessary operations in callback
// Bad: Creating new objects in callback
const badIndex = users.findIndex((user) => {
const processed = { ...user, checked: true }; // Unnecessary
return processed.active;
});
// Good: Direct property access
const goodIndex = users.findIndex((user) => user.active);
// 3. Use simple conditions when possible
// Complex condition
const complex = users.findIndex(
(user) =>
user.name.toLowerCase().startsWith('user') &&
user.id > 0 &&
user.active === true
);
// Simple condition (if applicable)
const simple = users.findIndex((user) => user.active);
Common Patterns
Finding and Replacing
function findAndReplace(array, predicate, replacement) {
const index = array.findIndex(predicate);
if (index !== -1) {
const original = array[index];
array[index] =
typeof replacement === 'function'
? replacement(original, index)
: replacement;
return { index, original, replaced: array[index] };
}
return null;
}
const items = [
{ id: 1, status: 'pending' },
{ id: 2, status: 'active' },
{ id: 3, status: 'pending' },
];
// Replace first pending item
const result = findAndReplace(
items,
(item) => item.status === 'pending',
(old) => ({ ...old, status: 'processing', updatedAt: Date.now() })
);
console.log(result);
// { index: 0, original: {...}, replaced: {...} }
Finding with Default
function findIndexOrDefault(array, predicate, defaultIndex = 0) {
const index = array.findIndex(predicate);
return index !== -1 ? index : defaultIndex;
}
const options = ['small', 'medium', 'large', 'extra-large'];
const selectedSize = 'large';
// Find selected index or default to first
const selectedIndex = findIndexOrDefault(
options,
(option) => option === selectedSize,
0
);
console.log(selectedIndex); // 2
Conclusion
The findIndex()
method is an essential tool for locating array elements based on complex conditions. It provides more flexibility than indexOf()
and complements find()
when you need the element's position rather than the element itself. Understanding when and how to use findIndex()
effectively can significantly improve your array manipulation code, making it more readable and maintainable.