Go filter slice
last modified October 30, 2026
In this article we show how to filter a slice in Go. A filtering operation walks a slice and keeps only the elements for which a predicate returns true.
A predicate is a function that takes one element and returns a boolean value. Go
has no built-in filter function; the operation is written with a
loop, with a generic helper, or with the functions from the slices
package.
All examples in this article were compiled and run with Go 1.27.0.
Filtering with a for loop
The most direct way to filter a slice is a for loop that appends
the matching elements to a second slice.
package main
import "fmt"
func main() {
vals := []int{-2, 0, 1, 9, 7, -3, -5, 6}
var positive []int
for _, val := range vals {
if val > 0 {
positive = append(positive, val)
}
}
fmt.Println(positive)
}
The loop tests every element of vals and copies the ones greater
than zero into positive. The original slice is not modified.
$ go run main.go [1 9 7 6]
A generic Filter function
The same loop can be wrapped in a generic function so that it works with any
element type. The result is allocated once with
make([]T, 0, len(vals)), which avoids repeated growth of the
backing array.
package main
import "fmt"
func Filter[T any](vals []T, pred func(T) bool) []T {
out := make([]T, 0, len(vals))
for _, val := range vals {
if pred(val) {
out = append(out, val)
}
}
return out
}
func main() {
vals := []int{-2, 0, 1, 9, 7, -3, -5, 6}
positive := Filter(vals, func(n int) bool { return n > 0 })
even := Filter(vals, func(n int) bool { return n%2 == 0 })
fmt.Println(positive)
fmt.Println(even)
}
Filter takes the slice and the predicate, and the type parameter
T is inferred from the call. Two different predicates are applied
to the same data.
$ go run main.go [1 9 7 6] [-2 0 6]
Filtering in place
When the original slice is not needed any more, the matching elements can be
moved to the front of the same backing array. A write index n
tracks the position of the next kept element.
package main
import "fmt"
func main() {
vals := []int{-2, 0, 1, 9, 7, -3, -5, 6}
backup := vals
n := 0
for _, val := range vals {
if val > 0 {
vals[n] = val
n++
}
}
vals = vals[:n]
fmt.Println("filtered:", vals, "len:", len(vals), "cap:", cap(vals))
fmt.Println("backup: ", backup)
fmt.Println("shared: ", &vals[0] == &backup[0])
}
No new array is allocated: backup, which still has length 8, sees
the compacted values followed by the leftovers in the tail. The capacity of the
result stays 8, and an append to it would overwrite the tail of the original.
$ go run main.go filtered: [1 9 7 6] len: 4 cap: 8 backup: [1 9 7 6 7 -3 -5 6] shared: true
Clearing the freed tail
If the element type contains pointers, the slots beyond the new length keep
references to objects that are otherwise unused. The garbage collector cannot
free them until the backing array itself goes away, so the tail is cleared with
the clear built-in.
package main
import "fmt"
func main() {
vals := []int{-2, 0, 1, 9, 7, -3, -5, 6}
backup := vals
n := 0
for _, val := range vals {
if val > 0 {
vals[n] = val
n++
}
}
clear(vals[n:])
vals = vals[:n]
fmt.Println("filtered:", vals)
fmt.Println("backup: ", backup)
}
clear zeroes the elements after position n in the
backing array. For integers the zero value is 0; for strings it is
"" and for pointers nil.
$ go run main.go filtered: [1 9 7 6] backup: [1 9 7 6 0 0 0 0]
Filtering strings by length
The len function returns the number of bytes, not characters. For
text with non-ASCII letters the two counts differ, and
utf8.RuneCountInString must be used when the number of characters
matters.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
words := []string{"kot", "łuk", "słoń", "dom", "żółw"}
var threeBytes []string
var threeRunes []string
for _, word := range words {
if len(word) == 3 {
threeBytes = append(threeBytes, word)
}
if utf8.RuneCountInString(word) == 3 {
threeRunes = append(threeRunes, word)
}
}
fmt.Println("bytes:", threeBytes)
fmt.Println("runes:", threeRunes)
}
łuk has three characters but four bytes, so it appears only in the
rune-based result. Both filters keep kot and dom.
$ go run main.go bytes: [kot dom] runes: [kot łuk dom]
Filtering a slice of structs
Elements of a struct slice are copied into the result, so the filtered slice holds independent values.
package main
import "fmt"
type User struct {
Name string
Occupation string
Country string
}
func main() {
users := []User{
{"John Doe", "gardener", "USA"},
{"Roger Roe", "driver", "UK"},
{"Paul Smith", "programmer", "Canada"},
{"Lucia Mala", "teacher", "Slovakia"},
{"Tim Welson", "programmer", "Canada"},
}
var programmers []User
for _, user := range users {
if user.Occupation == "programmer" {
programmers = append(programmers, user)
}
}
for _, u := range programmers {
fmt.Printf("%s (%s)\n", u.Name, u.Country)
}
}
The loop keeps the users whose Occupation equals
programmer and prints each of them with the country in parentheses.
$ go run main.go Paul Smith (Canada) Tim Welson (Canada)
Filtering with slices.DeleteFunc
The slices.DeleteFunc function removes all elements for which the
function returns true and returns the shortened slice. It works in place, like
the manual loop above.
package main
import (
"fmt"
"slices"
)
func main() {
vals := []int{-2, 0, 1, 9, 7, -3, -5, 6}
vals = slices.DeleteFunc(vals, func(n int) bool { return n <= 0 })
fmt.Println(vals, len(vals), cap(vals))
}
The predicate describes the elements to drop, which is the opposite of a filter predicate. The length shrinks to four elements while the capacity remains 8.
$ go run main.go [1 9 7 6] 4 8
Searching with ContainsFunc and IndexFunc
Sometimes only one element is needed. slices.ContainsFunc reports
whether any element matches the predicate, and slices.IndexFunc
returns the position of the first match, or -1.
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
words := []string{"sky", "forest", "fly", "cup", "wood", "falcon"}
fmt.Println(slices.ContainsFunc(words, func(w string) bool {
return strings.HasPrefix(w, "f")
}))
fmt.Println(slices.IndexFunc(words, func(w string) bool {
return strings.HasPrefix(w, "f")
}))
fmt.Println(slices.IndexFunc(words, func(w string) bool {
return strings.HasPrefix(w, "z")
}))
}
The word forest is at index 1, so the first two calls succeed. No
word starts with z, which produces -1.
$ go run main.go true 1 -1
Nil versus empty results
A filter that declares the result as var out []T returns
nil when nothing matches. A slice created with
make([]T, 0) is empty but not nil. Both print as [],
so the difference is visible only through a comparison with nil or
with encoding/json, which encodes a nil slice as null.
package main
import "fmt"
func positives(vals []int) []int {
var out []int
for _, val := range vals {
if val > 0 {
out = append(out, val)
}
}
return out
}
func main() {
fmt.Println(positives([]int{-1, -2}) == nil)
fmt.Println(positives([]int{1, 2}) == nil)
out := positives([]int{-1, -2})
fmt.Printf("%v len=%d nil=%t\n", out, len(out), out == nil)
empty := make([]int, 0)
fmt.Printf("%v len=%d nil=%t\n", empty, len(empty), empty == nil)
}
Both slices have length zero, but only the result of the filter without matches
is nil. Use make([]T, 0) when the caller should receive an empty
slice that can be encoded as a JSON array.
$ go run main.go true false [] len=0 nil=true [] len=0 nil=false
Filtering map entries
Maps can be filtered with maps.DeleteFunc. The function receives the
key and the value, and the key list of the remaining entries can be collected
with maps.Keys and sorted with slices.Sorted.
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
stock := map[string]int{
"apple": 12,
"banana": 0,
"cherry": 7,
"damson": 0,
}
available := maps.Clone(stock)
maps.DeleteFunc(available, func(_ string, n int) bool { return n == 0 })
names := slices.Sorted(maps.Keys(available))
fmt.Println(names)
fmt.Println(available["cherry"])
}
maps.Clone keeps the original map intact. Two entries have a zero
count and are removed; the remaining keys are sorted so that the output does not
depend on map iteration order.
$ go run main.go [apple cherry] 7
Source
Go slices package - reference
Go maps package - reference
In this article we have filtered slices in Go with loops, a generic helper, and
the slices and maps packages.
Author
List all Go tutorials.