slices.DeleteFunc removes elements selected by a predicate and compacts the retained values into the same slice storage. It is a filtering operation with mutation semantics: retained order stays intact, the returned slice can be shorter, and callers must use that returned slice header.
The generic signature accepts any slice element type:
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) SThe predicate describes deletion rather than retention. An element disappears when del returns true.
The predicate selects values to remove
A slice containing mixed status values can be filtered without building a second result slice explicitly:
package main
import (
"fmt"
"slices"
)
func main() {
values := []int{3, 4, 7, 8, 11, 12}
values = slices.DeleteFunc(values, func(v int) bool {
return v%2 != 0
})
fmt.Println(values) // [4 8 12]
}The odd values satisfy the deletion predicate, so the even values remain. Their relative order is unchanged.
This predicate direction is worth keeping explicit in names. A function named discard or remove tends to match the API contract more directly than a function named keep, which would require negating its result at the call site.
Compaction can rewrite the backing array
DeleteFunc modifies the supplied slice in place. Retained elements after a removed value may be moved toward lower indexes, so another slice that shares the same backing array can observe changed contents.
values := []string{"keep", "drop", "stay"}
alias := values
values = slices.DeleteFunc(values, func(v string) bool {
return v == "drop"
})
fmt.Println(values) // [keep stay]
fmt.Println(alias) // shares storage modified by compactionThe returned slice is the valid filtered view. The old slice header still carries its original length and should not be treated as an independent snapshot.
When the original sequence must remain available, cloning establishes separate outer storage before filtering:
filtered := slices.Clone(values)
filtered = slices.DeleteFunc(filtered, discard)For pointer, map, slice, or other reference-bearing element values, the clone remains shallow. Separate outer storage does not duplicate nested objects.
Obsolete tail slots are cleared
Compaction leaves positions beyond the new length that no longer belong to the returned slice. DeleteFunc sets those obsolete elements to the zero value for the element type.
That behavior is especially relevant for slices containing pointers or other values that can retain references. Removed tail entries do not remain stored solely as stale values beyond the new logical length.
type Entry struct {
Active bool
}
entries := []*Entry{
{Active: true},
{Active: false},
{Active: true},
}
entries = slices.DeleteFunc(entries, func(e *Entry) bool {
return !e.Active
})
fmt.Println(len(entries)) // 2The two active pointers remain in order. The obsolete slot created by compaction is zeroed.
An empty result preserves nil state
When every element is removed, the result is empty. The standard library contract preserves the input slice’s nil state for an empty result.
var nilValues []int
nilValues = slices.DeleteFunc(nilValues, func(int) bool { return true })
fmt.Println(nilValues == nil) // true
values := []int{1, 2}
values = slices.DeleteFunc(values, func(int) bool { return true })
fmt.Println(values == nil) // falseThis distinction can matter in code that intentionally treats nil and non-nil empty slices as different representations.
Predicate filtering differs from index deletion
slices.Delete removes one contiguous index range. slices.DeleteFunc instead evaluates elements against a condition and can remove matches spread throughout the slice.
For a known contiguous range, Delete expresses the operation directly and does not require a predicate. For condition-based filtering, DeleteFunc avoids manual index bookkeeping and performs the compaction as part of the operation.
Both functions can mutate the backing array and both return a slice header describing the valid result. Assigning that return value is therefore part of the operation rather than optional syntax.
Side effects make predicates harder to reason about
A deletion predicate can technically perform side effects, but its useful contract is simply to classify an element. Keeping it focused on that classification avoids coupling filtering behavior to unrelated mutable state.
A predicate that depends on external state can still be valid when that state is intentional and stable for the duration of the call. The key boundary is that the removal decision is made through the predicate, while storage compaction remains controlled by DeleteFunc.
slices.DeleteFunc fits cases where the desired result is the original sequence minus condition-matching elements and in-place storage reuse is acceptable. If the source must remain untouched, or filtering must produce independently owned nested values, that ownership requirement needs to be handled separately.