How easy would it be to remove item(s) from a slice, while iterating that slice? It seems to be a trivial task, so lets see how to accomplish this in Go programming language.

Coming from a Java landscape, I do remember that in Java you cannot remove items from a list directly. For example, following code will throw ConcurrentModificationException:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(String st:li){
        if(st.equalsIgnoreCase("str3"))
            li.remove("str3");
    }
    System.out.println(li);
}

Java provides Iterator.remove() method for such purposes.

Ok, no more Java, let’s see how to do this in Go. First of to remove an item from a slice you need to use built-in function append:

daysOfWeek.goplayground
1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
	daysOfWeek := []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
	// let's remove "Monday" from daysOfWeek
	daysOfWeek = append(daysOfWeek[:1], daysOfWeek[2:]...)
	fmt.Println(daysOfWeek) // [Sunday Tuesday Wednesday Thursday Friday Saturday]
}

Given the following code:

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

import "fmt"

type Person struct {
	Name string
	Remove bool
}

var people []Person

func init() {
	people = []Person{
		{"P0", false},
		{"P1", false},
		{"P2", false},
		{"P3", true},
	}
}

Let’s try to remove P3 person, while iterating the people slice with range loop:

withRange.goplayground
1
2
3
4
5
6
7
8
func main() {
	for i, p := range people {
		if p.Remove {
			people = append(people[:i], people[i+1:]...)
		}
	}
	fmt.Println(people) // [{P0 false} {P1 false} {P2 false}]
}

Looks like it works with single item marked to remove, but it will fail soon with panic: runtime error: slice bounds out of range, if there are more than one item to remove. This happens because the length of the people slice is decreasing with each successful remove operation. But the range loop iterates the initial len(people) times.

Now it is clear that we need to keep track of len(people) with each successful remove operation. Let’s try to accomplish this with plain old for loop:

withPlainOldForV1.goplayground
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

type Person struct {
	Name   string
	Remove bool
}

var people []Person

func init() {
	people = []Person{
		{"P0", false},
		{"P1", true},
		{"P2", false},
		{"P3", true},
	}
}

func main() {
	for i, rcount, rlen := 0, 0, len(people); i < rlen; i++ {
		j := i - rcount
		if people[j].Remove {
			people = append(people[:j], people[j+1:]...)
			rcount++
		}
	}
	fmt.Println(people) // [{P0 false} {P2 false}]
}

Well now it works! But why to keep extra counter rcount to track the number of remove operations, if we know that the length of people slice is decreasing with each successful remove operation? Bearing that in mind we can optimize to something like this:

withPlainOldForV2.goplayground
1
2
3
4
5
6
7
8
9
func main() {
	for i, rlen := 0, len(people); i < rlen; i++ {
		j := i - (rlen - len(people))
		if people[j].Remove {
			people = append(people[:j], people[j+1:]...)
		}
	}
	fmt.Println(people) // [{P0 false} {P2 false}]
}

But it still looks ugly, isn’t it? Well as a general rule of thumb, if something looks ugly, try to do it the opposite way. Why not? Let’s try:

withPlainOldForV3.goplayground
1
2
3
4
5
6
7
8
func main() {
	for i := len(people) - 1; i >= 0; i-- {
		if people[i].Remove {
			people = append(people[:i], people[i+1:]...)
		}
	}
	fmt.Println(people) // [{P0 false} {P2 false}]
}

Now, this version is clean and fully functional. Hope you find this useful.

Update: 26.04.2018

Following is simpler way to filter a slice without allocating a new underlying array:

filterSlice.goplayground
1
2
3
4
5
6
7
8
9
10
func main() {
	// create a zero-length slice with the same underlying array
	tmp := people[:0]
	for _, p := range people {
		if !p.Remove {
			tmp = append(tmp, p)
		}
	}
	fmt.Println(tmp) // [{P0 false} {P2 false}]
}

Recommended further reading: Slice expressions in Go