Removing Elements from Arrays the Right Way

Removing Elements from Arrays the Right Way

talha

Imagine you’re working with a simple array, one that contains 16 integers.

const eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];

Now, let’s say we need to weed out all numbers greater than 99 from our array. Sounds like a piece of cake, doesn’t it?

Your code will probably look like this,

JavaScript
1
2
3
4
5
6
7
8
const eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];

for (let i = 0; i < eg.length; i++) {
  if (eg[i] > 99) {
    // Remove element
    eg.splice(i, 1);
  }
}

But wait, once you print the updated array, you’ll notice something’s off.

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 233, 610];

Why are 233 and 610 still lounging in our array? They’re clearly above our cut-off of 99.

What went wrong?

ℹ️
Heads up: We’re using JavaScript for our examples, but the puzzle we’re tackling? It’s universal across all programming languages. Near the end, I have added examples in Golang, C#, C++, Python and JavaScript.

Let’s sprinkle our code with log statements to see what’s happening under the hood. You can view and run the code at this link.

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];

console.log(`index\tvalue\tarray length`);

for (let i = 0; i < eg.length; i++) {
  console.log(`${i}\t\t${eg[i]}\t\t\t${eg.length}`);

  if (eg[i] > 99) {
    // Remove element
    eg.splice(i, 1);
    console.log(`Element at ${i} removed. Length is  ${eg.length}`)
  }
}

console.log(eg)

The output is,

Output
index   value   array length
0       0           16
1       1           16
2       1           16
3       2           16
4       3           16
5       5           16
6       8           16
7       13          16
8       21          16
9       34          16
10      55          16
11      89          16
12      144         16
Element at 12 removed. Length is  15
13      377         15
Element at 13 removed. Length is  14
[ 0,   1,  1,  2,  3,  5, 8,  13, 21, 34, 55, 89, 233, 610 ]

Here’s where it gets interesting. When our loop hits index 12, it encounters the number 144. 144 is removed, which reduces array length to 15 from 16.

Then the index i is 14, and it reaches element 377. 377 is removed, which reduces array length from 15 to 14.

Then the index i reaches 15, but it fails the condition i < eg.length, which stops the result.

The loop ran only 14 times when it should have run 16 times. It happens because we modified the array length during the iteration.

⚠️
If you think forcing the loop to run 16 times will fix the issue, then you are wrong.

Have a look at following incorrect code.

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];

const len = eg.length;

console.log(`index\tvalue\tarray length`);

for (let i = 0; i < len; i++) {
  console.log(`${i}\t\t${eg[i]}\t\t\t${eg.length}`);

  if (eg[i] > 99) {
    // Remove element
    eg.splice(i, 1);
    console.log(`Element at ${i} removed. Length is  ${eg.length}`);
  }
}

If you try to run this code, you will get “index out of bound” runtime errors.

Now, how do we solve this issue? We have three ways to do it the right way.

Solution 1 – Decrement index

One way to solve this problem is to decrement i whenever an element from the array is removed. This way index does not skip over the remaining elements of the array.

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];

console.log(`index\tvalue\tarray length`);

for (let i = 0; i < eg.length; i++) {
  console.log(`${i}\t\t${eg[i]}\t\t\t${eg.length}`);

  if (eg[i] > 99) {
    // Remove element
    eg.splice(i, 1);
    console.log(`Element at ${i} removed. Length is  ${eg.length}`);
    i--;
  }
}

console.log(eg);

Its output is

Output
index   value   array length
0       0           16
1       1           16
2       1           16
3       2           16
4       3           16
5       5           16
6       8           16
7       13          16
8       21          16
9       34          16
10      55          16
11      89          16
12      144         16
Element at 12 removed. Length is  15
12      233         15
Element at 12 removed. Length is  14
12      377         14
Element at 12 removed. Length is  13
12      610         13
Element at 12 removed. Length is  12
[ 0,  1,  1,  2,  3, 5,  8, 13, 21, 34, 55, 89 ]

You can view and run this code here.

Solution 2 – Iterate in Reverse

In this solution, you start from the last element of the array and continue backwards. This way, even if array length is modified, the loop iterates over all the remaining elements.

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];

let i = eg.length;

console.log(`index\tvalue\tarray length`);

while (i--) {
  console.log(`${i}\t\t${eg[i]}\t\t\t${eg.length}`);

  if (eg[i] > 99) {
    // Remove element
    eg.splice(i, 1);
    console.log(`Element at ${i} removed. Length is ${eg.length}`);
  }
}

console.log(eg);

Its output is

Output
index   value   array length
15      610         16
Element at 15 removed. Length is 15
14      377         15
Element at 14 removed. Length is 14
13      233         14
Element at 13 removed. Length is 13
12      144         13
Element at 12 removed. Length is 12
11      89          12
10      55          12
9       34          12
8       21          12
7       13          12
6       8           12
5       5           12
4       3           12
3       2           12
2       1           12
1       1           12
0       0           12
[ 0,  1,  1,  2,  3, 5,  8, 13, 21, 34, 55, 89 ]

You can view and run this code here.

You can, of course, use a for loop in place of while.

for (i = eg.length - 1; i >= 0; i--) {}

You can view code with a for loop here.

Is this problem specific to some languages?

No! Whichever programming language you pick, if you iterate and remove array elements incorrectly, you will face this issue.

Here is the same problem replicated in Golang. If you run it, the program fails during runtime.

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func remove(slice []int, s int) []int {
    return append(slice[:s], slice[s+1:]...)
}

func main() {
    eg := []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610}
for i, el := range eg {
        if el > 99 {
            eg = remove(eg, i)
            fmt.Println(len(eg))
        }
    }

    fmt.Println(eg)
}

The output of the above program:

Output
./main
15
14
panic: runtime error: slice bounds out of range

goroutine 1 [running]:
main.remove(...)
    /home/runner/main.go:6
main.main()
    /home/runner/main.go:14 +0x25b
exit status 2

But if we turn around the code and start iteration is reverse, then the code works fine.

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func remove(slice []int, s int) []int {
    return append(slice[:s], slice[s+1:]...)
}

func main() {
    eg := []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610}
    l := len(eg) - 1
    for l >= 0 {
        if eg[l] > 99 {
                eg = remove(eg, l)
            }

        l = l - 1
    }

    fmt.Println(eg)
}

Best Solution

A succinct solution is to use functional programming.

Most modern programming languages are incorporating functional programming features. Thus, if a language supports new methods, you do not have to use the old fashioned loops to remove elements from an array.

In ECMA-262 5th edition, filter() method was added to the JavaScript.

We can rework the above example into a simple oneliner.

JavaScript
1
2
3
4
const eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];
result = eg.filter((x) => x < 99);
// Output
console.log(result);

Here is a working Python oneliner.

Python
1
2
3
eg = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
result = [x for x in eg if x < 99]
print(result)

Either of the following way will work in C#.

Using Linq.

C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using System;
using System.Collections.Generic;
using System.Linq;

class MainClass {
  public static void Main (string[] args) {
    List<int> eg = new List<int>(){0, 1, 1, 2, 3, 5, 8, 13,
                        21, 34, 55, 89, 144, 233, 377, 610};
    var result = (from x in eg where x < 99 select x).ToList();
    result.ForEach(i => Console.WriteLine(i));
  }
}

Using predicate.

C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using System;
using System.Collections.Generic;

class MainClass {
  public static void Main (string[] args) {
    List<int> eg = new List<int>(){0, 1, 1, 2, 3, 5, 8, 13,
                        21, 34, 55, 89, 144, 233, 377, 610};
    eg.RemoveAll(i => i > 99);
    eg.ForEach(i => Console.WriteLine(i));
  }
}

C++ has std::remove_if method.

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> eg = {0,  1,  1,  2,  3,   5,   8,   13,
                         21, 34, 55, 89, 144, 233, 377, 610};
  eg.erase(std::remove_if(
              eg.begin(),
              eg.end(),
              [](int x) {
                  return x > 99;
              }),
           eg.end());

  // Print result
  std::for_each(eg.begin(),
                eg.end(),
                [](const int &e) {
                    std::cout << e << " ";
                });
}

You can run this code here.

Last updated on