How to delete an element from a slice in Go
Removing an element from a slice is not particularly intuitive in Go. Unlike other languages, Go does not provide a built-in function for this purpose. If you’re wondering how an element may be removed from a Go slice, the code snippets below should provide the answers you seek.
Let’s consider a few strategies to remove elements from a slice in Go. The first two sections below assume that you want to modify the slice in place. If you want to create a copy of the slice with the element removed, while leaving the original as is, please jump to the Preserve the original slice section below.
If slice order is unimportant
If the order of the elements in a slice is not important, you can copy the last element to the index that you want to remove, then truncate the slice up until the last element as shown below:
package main
import (
"fmt"
)
// removeElement deletes the element at index `i`
// without preserving the order of the slice `s`.
// Note that the original slice is modified
func removeElement(s []int, i int) ([]int, error) {
// s is [1,2,3,4,5,6], i is 2
// perform bounds checking first to prevent a panic!
if i >= len(s) || i < 0 {
return nil, fmt.Errorf("Index is out of range. Index is %d with slice length %d", i, len(s))
}
// copy the last element (6) to index `i`. At this point,
// `s` will be [1,2,6,4,5,6]
s[i] = s[len(s)-1]
// Remove the last element from the slice by truncating it
// This way, `s` will now include all the elements from index 0
// up to (but not including) the last element
return s[:len(s)-1], nil
}
func main() {
s := []int{1, 2, 3, 4, 5, 6}
// Remove the element at the 2nd index (3)
s, err := removeElement(s, 2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(s) // [1,2,6,4,5]
// The capacity of the slice is preserved
// even though the length has changed
fmt.Println(cap(s)) // 6
}
A variation of the above solution which is potentially more readable is to copy the first element to the specified index, then truncate the slice from the second index. A subtle difference with this solution compared to the previous one is that the capacity of the slice is not preserved which makes this method potentially less performant if you’ll be appending to the slice later.
package main
import (
"fmt"
)
// removeElement deletes the element at index `i`
// without preserving the order of the slice `s`.
// Note that the original slice is modified
func removeElement(s []int, i int) ([]int, error) {
// s is [1,2,3,4,5,6], i is 2
// perform bounds checking first to prevent a panic!
if i >= len(s) || i < 0 {
return nil, fmt.Errorf("Index is out of range. Index is %d with slice length %d", i, len(s))
}
// copy first element (1) to index `i`. At this point,
// `s` will be [1,2,1,4,5,6]
s[i] = s[0]
// Remove the first element from the slice by truncating it
// This way, `s` will now include all the elements from index 1
// until the element at the last index
return s[1:], nil
}
func main() {
s := []int{1, 2, 3, 4, 5, 6}
// Remove the element at the 2nd index (3)
s, err := removeElement(s, 2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(s) // [2,1,4,5,6]
// The capacity of the slice is reduced
fmt.Println(cap(s)) // 5
In general, prefer the first approach to the second unless you’re sure you won’t be appending to the slice later.
If slice order is important
If you want to maintain the order of the elements in the slice, you can use the following technique known as re-slicing which leverages the built-in append function:
package main
import (
"fmt"
)
// removeElement deletes the element at index `i`
// and preserves the order of the slice `s`.
// Note that the original slice is modified
func removeElement(s []int, i int) ([]int, error) {
// s is [1,2,3,4,5,6], i is 2
// perform bounds checking first to prevent a panic!
if i >= len(s) || i < 0 {
return nil, fmt.Errorf("Index is out of range. Index is %d with slice length %d", i, len(s))
}
// This creates a new slice by creating 2 slices from the original:
// s[:i] -> [1, 2]
// s[i+1:] -> [4, 5, 6]
// and joining them together using `append`
return append(s[:i], s[i+1:]...), nil
}
func main() {
s := []int{1, 2, 3, 4, 5, 6}
// Remove the element at the 2nd index (3)
s, err := removeElement(s, 2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(s) // [1,2,4,5,6]. 3 is removed but the original order is preserved
}
This method is more expensive than the first one because you’re joining two new slices together to create a third one using append
as opposed to one assignment and one copy in the previous section.
Preserve the original slice
Both solutions discussed above modify the original slice in one way or another. If this is not desired, you need to create a brand new slice with a different underlying array before appending to it. Make sure to assign the result of removeElement
to a new variable as well to maintain access to the original.
Here’s a variant of the re-slicing method that leaves the original slice passed to removeElement
unmodified:
package main
import (
"fmt"
)
// removeElement deletes the element at index `i`
// and preserves the order of the slice `s`.
// The original slice is not modified here
func removeElement(s []int, i int) ([]int, error) {
// s is [1,2,3,4,5,6], i is 2
// perform bounds checking first to prevent a panic!
if i >= len(s) || i < 0 {
return nil, fmt.Errorf("Index is out of range. Index is %d with slice length %d", i, len(s))
}
// Create a brand new slice first.
// This slice has a different underlying array to `s`
newSlice := make([]int, 0)
// copy the elements in `s` to `newSlice`
// up until (but excluding) index `i`
newSlice = append(newSlice, s[:i]...) // [1, 2]
// Copy the elements in `s` after index `i`
// up until the end of slice `s` to `newSlice`
return append(newSlice, s[i+1:]...), nil
}
func main() {
s := []int{1, 2, 3, 4, 5, 6}
// Remove the element at the 2nd index (3)
// and assign the resulting slice to `ns`
ns, err := removeElement(s, 2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(s) // [1,2,3,4,5,6]: original slice is preserved
fmt.Println(ns) // [1,2,4,5,6]
ns[0] = 100
fmt.Println(s) // [1,2,3,4,5,6]: s remains unchanged
fmt.Println(ns) // [100,2,4,5,6]
}
As you can see, this solution preserves the original s
slice after removeElement
is called, and further modifications of ns
after the fact doesn’t cause s
to change as well.
Remove elements from a slice while iterating
If you want to remove multiple elements from a slice in Go while iterating over it, make sure that you’re iterating from the end of the slice to the start. If you do it the other way round, your program will panic with an index out of bounds error due to the fact that the slice length keeps decreasing each time an element is removed.
Here’s an example that removes all even integers from a slice:
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4, 5, 6}
for i := len(s) - 1; i >= 0; i-- {
// this checks whether the integer is even
// before removing it from the slice
if s[i]%2 == 0 {
s = append(s[:i], s[i+1:]...)
}
}
fmt.Println(s) // [1,3,5]
}
Conclusion
You should be able to use the techniques presented above to solve any problem that involves removing an element from a slice in Go. If you have any further contribution, please leave a comment below.
Thanks for reading, and happy coding!