How do you delete elements from a slice efficiently, and what changed with slices.Delete in Go 1.22?
Question 50MediumGo 1.22 to 1.25
There are three common patterns:
- Order-preserving delete: O(n), using append or copy.
- Unordered delete: O(1), swap with the last element.
- In-place filter: O(n), no allocation.
// Order-preserving
s = append(s[:i], s[i+1:]...) // or s = slices.Delete(s, i, i+1)
// O(1) unordered
s[i] = s[len(s)-1]
s = s[:len(s)-1]
// In-place filter, reusing the backing array
func filter[T any](s []T, keep func(T) bool) []T {
out := s[:0]
for _, v := range s {
if keep(v) {
out = append(out, v)
}
}
clear(s[len(out):]) // zero the tail so the GC can free pointers
return out
}
// Library equivalent: s = slices.DeleteFunc(s, func(v T) bool { return !keep(v) })
Go 1.22 change: slices.Delete, DeleteFunc, Compact, CompactFunc and Replace now zero the obsolete tail elements between the new len and the old len. This fixes the pointer-retention leak. It also breaks code that kept the old slice header and read those elements. They are now zero values, not stale data. Always use the returned slice: s = slices.Delete(s, i, j).
append(s[:i], s[i+1:]...) has the same aliasing issue. It changes the shared array, so any other slice over it sees shifted data.
More on Arrays, Slices, Maps & Strings
- Q48What does this print? (range over an array vs a slice)
- Q49How can subslicing cause memory leaks, and how do you fix them?
- Q51Why can't you compare slices with ==? How do you check equality of slices, maps, and structs containing them?
- Q52How do you correctly allocate a 2D slice, and what are the performance trade-offs?
- Q53Why do the slices package functions use the constraint [S ~[]E, E any] instead of just []E?
- Q54Which slices and maps package functions do you use daily, and how do they work with iterators (Go 1.23)?