Finding All Indices of Array Element in JavaScript


In the blogpost, we’ll learn multiple ways of collecting indices where a target element lies in a JavaScript array. We’ll also learn few common mistakes and lastly, solidify our understanding with a relevant practice question.

Lets dive in! 👨‍💻


Ways to find all indices of array element in JavaScript

Using Array.prototype.indexOf in a Loop

The indexOf method can locate the first occurrence of an element within an array. It takes two parameters: the element to search for, and an optional index from which to start the search. By using indexOf in a loop, we effectively search for all occurrences of a specified element.

In following example, indexOf is initially called without the start index, which defaults the search to begin from the start of the array. Once an occurrence is found, the loop continues by calling indexOf again, but this time starting from the position right after the last found index. This process repeats until indexOf returns -1, meaning no more occurrences are found. This approach is straightforward and doesn’t require much coding, but it’s important to increment the index for each subsequent call to avoid an infinite loop.

Using Array.prototype.indexOf in loop (JavaScript)
let array = [1, 2, 3, 2, 4];
let target = 2;
let indexes = [];
let idx = array.indexOf(target);

while (idx != -1) {
  indexes.push(idx);
  idx = array.indexOf(target, idx + 1);
}

console.log(indexes); // Output: [1, 3]

Custom Loop with Conditional Check

Using a custom loop like the for loop provides greater control over the iteration process. In this method, every element of the array is explicitly checked against the target value. When a match is found, the current index is stored in the indexes array.

This method is more verbose than using built-in array methods but offers clearer insight into the iteration process, and flexibility to introduce additional logic (modify on fly, break loop, etc).

Loop with Conditional Check (JavaScript)
let array = [1, 2, 3, 2, 4];
let target = 2;
let indexes = [];

for (let i = 0; i < array.length; i++) {
  if (array[i] === target) {
    indexes.push(i);
  }
}

console.log(indexes); // Output: [1, 3]

Array.prototype.filter with Index Tracking

The combination of map and filter methods offers a functional approach to solving the problem. map transforms each element of the array and returns a new array of the same length. In this case, it’s used to create an array where each element is either its index (if it matches the target) or -1.

The filter method then processes this array, keeping only those elements (indexes) that are not -1. This chain of methods exemplifies a more declarative approach to programming, often making the code more readable and concise. However, it may be less performant than a simple loop for large arrays, as it involves creating an intermediate array and then filtering it.

Array.prototype.filter with Index Tracking (JavaScript)
let array = [1, 2, 3, 2, 4];
let target = 2;
let indexes = array
  .map((element, index) => (element === target ? index : -1))
  .filter(index => index !== -1);

console.log(indexes); // Output: [1, 3]

Array.prototype.forEach

forEach is a method that executes a provided function once for each element in an array. It’s an alternative to the traditional for loop, offering a more functional style of iterating over arrays. In the context of our problem, forEach is used to traverse the array, and a conditional inside the callback function checks if the current element matches the target.

This method is cleaner and more expressive compared to a standard for loop. It abstracts away the management of the loop counter and exit condition, allowing the developer to focus on the logic applied to each element. However, one limitation of forEach is that it cannot be stopped or broken out of, unlike traditional loops. Therefore, it’s more suitable for scenarios where every element of the array needs to be examined.

Array.prototype.forEach (JavaScript)
let array = [1, 2, 3, 2, 4];
let target = 2;
let indexes = [];

array.forEach((element, index) => {
  if (element === target) {
    indexes.push(index);
  }
});

console.log(indexes); // Output: [1, 3]

Array.prototype.reduce for Index Accumulation

The reduce function, is primarily used for reducing an array into a single value. However, it can also be used to accumulate data based on array elements, such as collecting indexes of specific items.

In following example, reduce traverses the array, and the callback function checks each element. If the element matches the target, its index is added to the accumulator (acc). Its effective in scenarios where you need to compile a list or collect data points based on certain criteria within an array.

Array.prototype.reduce for Index Accumulation (JavaScript)
let array = [1, 2, 3, 2, 4];
let target = 2;
let indexes = array.reduce((acc, curr, index) => {
    if (curr === target) {
        acc.push(index);
    }
    return acc;
}, []);

console.log(indexes); // Output: [1, 3]

Array.prototype.flatMap

flatMap is a method in JavaScript that merges both map and flatten operations into one. It is particularly valuable for cases where you need to map each element to another value or format and then flatten the resulting structure into a single array.

Lets start with an introductory example of flatMap first. In following code, flatMap is used to handle an array that contains both individual elements and nested arrays. The callback function checks if an element is an array itself. If so, it returns the element (array) as is; if not, it wraps the element in an array. The flatMap method then automatically flattens the resulting structure.

flatMap intro (JavaScript)
let nestedArray = [1, [2, 3], [4, 5]];
let flatMappedArray = nestedArray.flatMap(element => {
    return Array.isArray(element) ? element : [element];
});

console.log(flatMappedArray); // Output: [1, 2, 3, 4, 5]

Now, lets apply flatMap to our use case. In following code, flatMap is used to search for a specific element within an array, mapping each occurrence to its index, while non-matching elements are mapped to an empty array. The automatic flattening feature of flatMap then removes these empty arrays, resulting in an array that contains only the indexes of the targeted element.

flatMap to find indices (JavaScript)
let array = [1, 2, 3, 2, 4];
let target = 2;
let indexes = array.flatMap((element, index) =>
  element === target ? [index] : [],
);

console.log(indexes); // Output: [1, 3]

Recursive Function Approach

Recursive functions call themselves with modified parameters, making them ideal for complex, iterative tasks. For index tracking, recursion can efficiently traverse an array, accumulating indexes as it goes.

Following recursive function keeps track of the current index and found indexes. Each call checks if the current element matches the target, accumulating matching indexes. The recursion ends when the entire array has been traversed.

Recursive approach (JavaScript)
function findAllIndexes(array, target, index = 0, indexes = []) {
    if (index >= array.length) return indexes;
    if (array[index] === target) indexes.push(index);
    return findAllIndexes(array, target, index + 1, indexes);
}

let array = [1, 2, 3, 2, 4];
let target = 2;
let indexes = findAllIndexes(array, target);
console.log(indexes); // Output: [1, 3]

Binary Search for Sorted Arrays

Binary search significantly reduces the time complexity in sorted arrays compared to linear searches. The basic principle of binary search is to divide and conquer, repeatedly narrowing down the search area by half.

In following example, once the target element is found, the algorithm doesn’t stop but continues to check adjacent elements. This approach is crucial because the same element might appear consecutively in sorted arrays. The binary search initially finds one instance of the target and then uses linear search to find other occurrences nearby. This hybrid approach maintains the efficiency of binary search while ensuring all instances of the target element are found.

Binary search sorted arrays (JavaScript)
function binarySearchIndexes(array, target) {
    let start = 0, end = array.length - 1, indexes = [];
    while (start <= end) {
        let mid = Math.floor((start + end) / 2);
        if (array[mid] === target) {
            indexes.push(mid);
            // Search for additional matches around the found index
            let left = mid - 1, right = mid + 1;
            while (array[left] === target) {
                indexes.push(left--);
            }
            while (array[right] === target) {
                indexes.push(right++);
            }
            // Sorting the indexes for proper order
            return indexes.sort((a, b) => a - b);
        }
        if (array[mid] < target) start = mid + 1;
        else end = mid - 1;
    }
    return indexes;
}

let sortedArray = [1, 2, 2, 2, 3, 4];
let target = 2;
let indexes = binarySearchIndexes(sortedArray, target);
console.log(indexes); // Output: [1, 2, 3]

Hash Map for Index Tracking

Hash maps, or objects in JavaScript, store key-value pairs efficiently. They are ideal for tracking indices of elements, especially in large or complex data sets.

In following code, we go one step further, and find indices of all unique values in the array. A hash map (indexMap) is created to store the indexes of each element in the array. Then as the array is iterated through, each element’s index is appended to an array in the map, keyed by the element itself. If the key does not exist in the map, it’s initialized to an empty array. This approach is useful for complex data sets where elements can occur multiple times, and we need to keep track of all their positions. It’s also highly efficient in terms of lookup and retrieval, as hash maps offer constant time complexity for these operations.

HashMap index tracking (JavaScript)
let array = ['a', 'b', 'a', 'c', 'b'];
let indexMap = array.reduce((acc, element, index) => {
    if (!acc[element]) acc[element] = [];
    acc[element].push(index);
    return acc;
}, {});

console.log(indexMap); // Output: { a: [0, 2], b: [1, 4], c: [3] }

Tips & errors while finding all indices of array element in JavaScript

Off-by-One Errors

These errors occur when loop conditions are incorrectly set, leading to either skipping elements or accessing invalid indexes.

Off by one error (JavaScript)
let array = [1, 2, 3, 4];

// ❌ Incorrect loop, misses the last element
for (let i = 0; i <= array.length - 1; i++) {
    console.log(array[i]); // Misses last element
}
// ❌ Incorrect loop, misses the first element
for (let i = 1; i < array.length; i++) {
    console.log(array[i]); // Misses firstelement
}
// ❌ Incorrect loop, index out of bound error (i==array.length)
for (let i = 0; i <= array.length; i++) {
    console.log(array[i]); // Index Out of Bounds error.
}

// ✅ Correct loop
for (let i = 0; i < array.length; i++) {
    console.log(array[i]); // Includes all elements
}

Mutating Arrays During Iteration:

Modifying an array during iteration (like adding or removing elements) can cause unpredictable results due to the change in array length.

Mutation during Iteration (JavaScript)
let array = [1, 2, 3, 4];
array.forEach((element, index) => {
    if (element === 2) {
        array.push(5); // Modifying array during iteration
    }
    console.log(element); // Unpredictable behavior
});

Some Array methods dont affect the original array

Methods like map, filter, and reduce return new arrays. It’s important to remember that they do not modify the original array.

Original array unchanged (JavaScript)
let array = [1, 2, 3, 4];
array.map(element => element * 2); // This operation's result is not saved
console.log(array); // Original array is unchanged

let doubledArray = array.map(element => element * 2);
console.log(doubledArray); // New array with doubled values

Incorrect Use of indexOf

The indexOf method in JavaScript returns -1 if the specified element is not found in the array. A common error is mistaking this -1 return value for a valid array index.

indexOf -1 check (JavaScript)
let array = [1, 2, 3, 4];
let index = array.indexOf(5); // 5 is not in the array
if (index !== -1) {
    console.log(`Found at index: ${index}`);
} else {
    console.log("Element not found"); // Correct handling
}

Binary Search on Unsorted Arrays:

Binary search is an efficient algorithm for finding elements but requires the array to be sorted. Applying it to unsorted arrays can lead to incorrect results.

Binary-Search unsorted Arrays (JavaScript)
let sortedArray = [1, 2, 3, 4, 5]; // Sorted array
let unsortedArray = [3, 1, 4, 5, 2]; // Unsorted array
// Applying binary search on both arrays for '3'
// sortedArray would give correct result, unsortedArray would not

Overlooking flatMap Depth Limit:

The flatMap method in JavaScript only flattens arrays one level deep. It’s not suitable for deeper nested arrays without additional flattening methods.

flatMap depth (JavaScript)
let nestedArray = [1, [2, [3, 4]]];
let flatMapResult = nestedArray.flatMap(element => element);
console.log(flatMapResult); 
// Output: [1, 2, [3, 4]] - Still nested

// For deeper flattening, use map followed by flat with appropriate depth
let flatResult = nestedArray.flat(2); 
console.log(flatResult); // Output: [1, 2, 3, 4]

🧪Practice Coding Problem: Array element indexer

Your turn now!😃 Lets test our understanding by solving a problem.

Write a function that takes an array of integers and an integer target as inputs. The function should return an object where the key is the target, and the corresponding value is an array of all indexes where that number appears in the array. Return empty object, if target not present in the array.

Problem (JavaScript)
function organizeElementIndexes(array, target) {
  // > > > 👉 Write code here 👈 < < <
}

// Example usage
let result = organizeElementIndexes([1, 2, 3, 2, 4, 2, 3], 2);
console.log(result); // Output: {2: [1, 3, 5]}
Please attempt before seeing the Answer:
Problem (JavaScript)
function organizeElementIndexes(array, target) {
    return array.reduce((acc, element, index) => {
        if (element === target) {
            if (!acc[element]) acc[element] = [];
            acc[element].push(index);
        }
        return acc;
    }, {});
}

Explanation:

  • The function organizeElementIndexes uses the reduce method to traverse the array.
  • For each element, it checks if the element matches the target.
  • If it does, the element is added as a key to the accumulator object (acc) if not already present. The index of the element is then pushed into the array corresponding to this key.
  • This process repeats for each element, accumulating the indexes of the target element.
  • Finally, the function returns the accumulator object containing the desired mapping.

Now you an expert at finding indices for all the target elements in an array.

Keep learning, and keep coding! 🚀👨‍💻

Scroll to Top