1console.log("hello-world);
javascript
console.log("hello-world);
hidden codeblock for CSS
css
.container {
  width: 80%;
}
hidden codeblock for CSS
html
<pre><code class="language-css">
.container {
  width: 80%;
}
</code></pre>
hidden codeblock for CSS
1console.log("hello-world);
javascript
console.log("hello-world);
hidden codeblock for CSS
css
.container {
  width: 80%;
}
hidden codeblock for CSS
html
<pre><code class="language-css">
.container {
  width: 80%;
}
</code></pre>
hidden codeblock for CSS

Finding All Indices of Array Element in JavaScript

ReadTime: 13 minutes

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.

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]
Using Array.prototype.indexOf in loop (JavaScript)

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).

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]
Loop with Conditional Check (JavaScript)

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.

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.filter with Index Tracking (JavaScript)

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.

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.forEach (JavaScript)

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.

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.reduce for Index Accumulation (JavaScript)

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.

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]
flatMap intro (JavaScript)

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.

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]
flatMap to find indices (JavaScript)

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.

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]
Recursive approach (JavaScript)

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.

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]
Binary search sorted arrays (JavaScript)

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.

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] }
HashMap index tracking (JavaScript)

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.

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
}
Off by one error (JavaScript)

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.

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
});
Mutation during Iteration (JavaScript)

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.

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
Original array unchanged

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.

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
}
Incorrect use of indexOf (-1 check)

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)

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
Binary search on unsorted arrays

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.

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]
Overlooking flatMap depth limit

🧪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)

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]}
Problem Code

Please attempt before seeing the Answer:

Solution
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;
  }, {});
}
Solution (JavaScript)

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! 🚀👨‍💻

Contact

Feel free to reach out!