A loop completes but leaves some targeted elements behind. splice shifts later elements left while a forward loop still increments its index. An item can move past the position being examined without being checked.
1. Reproduce the bug with adjacent even numbers
const values = [2, 4, 5, 6, 8];
const visited = [];
for (let index = 0; index < values.length; index += 1) {
visited.push(values[index]);
if (values[index] % 2 === 0) values.splice(index, 1);
}
console.log(JSON.stringify(values)); // [4,5,8]
console.log(visited.join('|')); // 2|5|6
Removing 2 moves 4 into index 0, but the next iteration examines index 1. Removing 6 similarly shifts 8 into the deleted position and skips it. splice mutates the source array; see MDN splice.
Using the original length as the loop bound does not fix index shifting. The visited log shows which values were actually inspected rather than merely counting iterations.
2. Create a new list when selection is the goal
const values = [2, 4, 5, 6, 8];
const remaining = values.filter(value => value % 2 !== 0);
console.log(JSON.stringify(remaining)); // [5]
console.log(JSON.stringify(values)); // [2,4,5,6,8]
console.log(remaining === values); // false
filter expresses which elements to keep and creates a new array; see MDN filter. This predicate only checks numbers without modifying the source.
These are dense arrays of already validated numbers. For object records, filter does not deep-copy each retained object. Other owners of those objects can still observe field mutations. A new list and deeply independent data are separate requirements.
3. Iterate backward when the same array must change
function removeEvensInPlace(values) {
for (let index = values.length - 1; index >= 0; index -= 1) {
if (values[index] % 2 === 0) values.splice(index, 1);
}
return values;
}
const values = [2, 4, 5, 6, 8];
const shared = values;
const remaining = removeEvensInPlace(values);
console.log(JSON.stringify(values)); // [5]
console.log(remaining === shared); // true
console.log(shared.length); // 1
This sample helper expects a writable dense array of previously validated numbers. After removing the current item, all unexamined positions have smaller indices. Moving backward avoids skipping them in this algorithm.
shared observes the new contents because it is still the same array. This violates a source-preservation requirement even though the retained values are correct. Conversely, assigning a variable to a new filtered array does not update other references to the original.
Backward traversal also reverses inspection order. Adding API calls or other side effects changes their sequence compared with a forward loop. Separate data selection from ordered work if action order matters.
4. Choose according to data ownership
| Requirement | Approach shown here |
|---|---|
| Preserve the source list | filter with a non-mutating predicate |
| Update the same shared array | Backward traversal with indexed splice |
| Preserve side-effect order | Separate selection from execution |
Test adjacent removals, first and last entries, all removed, none removed and empty input. Assert values, order and identity according to the chosen contract.
All six bilingual blocks ran with Node.js, alongside boundary cases for backward removal. These approaches were not benchmarked or integrated with UI components.




No comments yet. Be the first to share your thoughts.