JavaScript Array.forEach — Modifying Arrays While Looping?
This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Modifying Array During Foreach Skip Bug and improve your technical interview readiness.
const arr = [1, 2, 3]
arr.forEach((item, index) => {
if (item === 2) arr.splice(index, 1)
})
console.log(arr)
Detailed Explanation
Why This Question Matters
If you've been coding in JavaScript for a while, you've probably used forEach a thousand times. It feels safe, it's clean, and it's the go-to for iterating over a list. But there is a nasty trap hidden in how forEach handles the array index, especially when you start mutating the array inside the loop.
The confusion usually stems from a mental model error: we assume that the loop "knows" the array has changed. In reality, the loop is just following a pre-determined set of indices. When you remove an item, the rest of the array shifts, but the loop counter keeps moving forward. This leads to the classic "skipped element" bug that can haunt your production code for weeks.
Understanding the Code
Let's look at the snippet:
On the surface, it looks simple. We want to find the number 2 and remove it. But here is what's actually happening under the hood:
1. Iteration 0: The loop starts at index 0. The value is 1. Nothing happens.
2. Iteration 1: The loop moves to index 1. The value is 2. The condition item === 2 is true, so arr.splice(1, 1) is called. The number 2 is removed.
3. The Shift: Now, the array is [1, 3]. The value 3, which was at index 2, has now shifted down to index 1.
4. Iteration 2: The forEach mechanism moves to the next index in the sequence: index 2.
Wait. The array only has two elements now (indices 0 and 1). There is no index 2. The loop finishes.
In this specific example, it looks like it worked because we removed the middle element and the last element stayed put. But if you tried to remove two consecutive elements, you'd realize the loop skips the element immediately following the one you deleted.
Finding the Correct Answer
The correct answer is Option B: [1, 3].
Why not other results? Some beginners think forEach might crash or throw an error when the array length changes. It doesn't. JavaScript is pretty forgiving here; it just keeps trying to hit the next index.
If the array had been [1, 2, 2, 3] and we tried to remove all 2s, the result would be [1, 2, 3]. The second 2 would be skipped entirely because the first 2 was deleted, shifting the second 2 into the index the loop had already processed.
Common Mistakes Developers Make
The biggest mistake is assuming forEach is a "smart" iterator. It isn't. It's essentially a wrapper around a standard for loop.
Another common pitfall is using splice inside a map or filter. While filter is the correct tool for removing items, developers sometimes try to "optimize" by mutating the original array inside a map call. This is a recipe for disaster because map expects to return a new array of the same length.
The "index shift" is the core of the problem. Whenever you use a method that changes the length of the array (like splice, pop, or shift) while iterating forward, you are moving the rug out from under your own feet.
Real-World Usage
In a real production environment, you'll rarely see a simple array of numbers like [1, 2, 3]. You'll be dealing with arrays of objects—maybe a list of active user sessions or a queue of pending API requests.
Imagine you're looping through a list of "expired" tokens and removing them from a cache. If you use forEach and splice, you'll accidentally leave half of the expired tokens in the cache because the loop skipped them. This could lead to memory leaks or authentication bugs that are incredibly hard to debug because they only happen when two expired tokens are adjacent in the array.
The professional way to handle this is to avoid mutation during iteration. Instead of splice, use .filter():
filter creates a new array, leaving the original intact and avoiding the index-shifting nightmare entirely. If you absolutely must mutate the original array for performance reasons (which is rare in JS), loop backwards:
Key Takeaways
- forEach does not track changes to the array length in real-time.
- Using splice inside a forward loop causes the next element to be skipped.
- Mutating data while iterating over it is generally a bad practice.
- Use .filter() for a cleaner, functional approach to removing elements.
- If you must mutate, loop backwards to keep your indices stable.
Why this matters
Understanding Modifying Array During Foreach Skip Bug is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.