JavaScript Basics

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.

By JavaScript Document Team
arraysmethodsbasicssearches6

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 processed
    • index (optional): The index of the current element
    • array (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.