Go filter and map
last modified October 30, 2026
In this article we show how to implement filter and map operations in Go. A filter keeps the elements of a collection for which a predicate returns true. A map applies a function to every element and collects the results in a new collection.
Go has no built-in filter or map function for slices. With generics, both can be written once and used with any element type. The examples were compiled and run with Go 1.27.0.
Filtering strings
A filter is a loop with a condition. The predicate is passed as a function value, so the same loop can be reused with different conditions.
package main
import (
"fmt"
"strings"
)
func Filter(data []string, f func(string) bool) []string {
out := make([]string, 0, len(data))
for _, v := range data {
if f(v) {
out = append(out, v)
}
}
return out
}
func main() {
words := []string{"war", "water", "cup", "tree", "storm", "wind"}
res := Filter(words, func(s string) bool {
return strings.HasPrefix(s, "w")
})
fmt.Println(res)
}
The result keeps the order of the input. The function allocates a new slice with enough capacity for every element, so the append operations do not reallocate in this loop.
$ go run main.go [war water wind]
Filtering structs
The same pattern works with structs. The predicate receives a copy of the element and can inspect any field.
package main
import "fmt"
type User struct {
Name string
Occupation string
Country string
}
func Filter(data []User, f func(User) bool) []User {
out := make([]User, 0, len(data))
for _, v := range data {
if f(v) {
out = append(out, v)
}
}
return out
}
func main() {
users := []User{
{"John Doe", "gardener", "USA"},
{"Roger Roe", "driver", "UK"},
{"Paul Smith", "programmer", "Canada"},
{"Lucia Mala", "teacher", "Slovakia"},
{"Patrick Connor", "shopkeeper", "USA"},
{"Tim Welson", "programmer", "Canada"},
{"Tomas Smutny", "programmer", "Slovakia"},
}
res := Filter(users, func(u User) bool {
return u.Country == "Slovakia"
})
for _, u := range res {
fmt.Printf("%-14s %s\n", u.Name, u.Occupation)
}
}
The predicate compares the Country field, and the result contains
the two users from Slovakia. The occupation is printed to show that the struct
values were copied into the new slice.
$ go run main.go Lucia Mala teacher Tomas Smutny programmer
A generic filter
A type parameter replaces the element type, so one function serves strings,
integers and any other type. The compiler infers T from the
argument.
package main
import (
"fmt"
"strings"
)
func Filter[T any](data []T, f func(T) bool) []T {
out := make([]T, 0, len(data))
for _, v := range data {
if f(v) {
out = append(out, v)
}
}
return out
}
func main() {
words := []string{"war", "cup", "water", "tree", "storm"}
fmt.Println(Filter(words, func(s string) bool {
return strings.HasPrefix(s, "w")
}))
nums := []int{-3, -1, 0, 2, 5, 7}
fmt.Println(Filter(nums, func(n int) bool {
return n > 0
}))
}
The two calls use different element types and different predicates. The
constraint any is enough because the filter only assigns and
compares nothing itself.
$ go run main.go [war water] [2 5 7]
Filtering in place
When the original slice is no longer needed, slices.DeleteFunc
removes the elements that match a predicate without allocating a second slice.
It shifts the remaining elements and returns the shortened slice.
package main
import (
"fmt"
"slices"
)
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
evens := slices.DeleteFunc(slices.Clone(nums), func(n int) bool {
return n%2 != 0
})
fmt.Println(evens)
fmt.Println("original:", nums)
}
slices.Clone protects the original slice in this example. Without
the clone, DeleteFunc would overwrite the elements of
nums and return a shorter slice that shares the same array.
$ go run main.go [2 4 6 8] original: [1 2 3 4 5 6 7 8]
Mapping integers
A map function allocates a result slice of the same length and assigns every transformed value to the same index.
package main
import "fmt"
func Map(data []int, f func(int) int) []int {
out := make([]int, len(data))
for i, v := range data {
out[i] = f(v)
}
return out
}
func main() {
nums := []int{1, 2, 3, 4, 5}
squares := Map(nums, func(n int) int {
return n * n
})
fmt.Println(squares)
}
Because the output has a fixed length, the function uses
make([]int, len(data)) and indexed assignment instead of
append. The input slice is not modified.
$ go run main.go [1 4 9 16 25]
Mapping strings
An existing function can be used as the mapping function when its signature
matches. strings.ToUpper takes and returns a string, so it can be
passed directly.
package main
import (
"fmt"
"strings"
)
func Map(data []string, f func(string) string) []string {
out := make([]string, len(data))
for i, v := range data {
out[i] = f(v)
}
return out
}
func main() {
words := []string{"war", "water", "cup", "tree", "storm"}
fmt.Println(Map(words, strings.ToUpper))
}
Passing a named function is the same as passing an anonymous function with the same body. The elements are transformed independently, so the order is preserved.
$ go run main.go [WAR WATER CUP TREE STORM]
A generic map
A generic map has two type parameters: the input element type T
and the output element type U. This allows a map that changes the
type, for example from an integer to its decimal representation.
package main
import (
"fmt"
"strconv"
)
func Map[T, U any](data []T, f func(T) U) []U {
out := make([]U, 0, len(data))
for _, v := range data {
out = append(out, f(v))
}
return out
}
func main() {
nums := []int{1, 2, 3, 4, 5}
labels := Map(nums, func(n int) string {
return "#" + strconv.Itoa(n)
})
fmt.Println(labels)
fmt.Printf("%T\n", labels)
}
The compiler infers T from the input slice and U from
the return type of the function. The printed type confirms that the result is
a slice of strings.
$ go run main.go [#1 #2 #3 #4 #5] []string
Combining filter and map
Filter and map compose naturally: the output of one is the input of the other. The intermediate slice exists only between the two calls.
package main
import (
"fmt"
"strconv"
"strings"
)
func Filter[T any](data []T, f func(T) bool) []T {
out := make([]T, 0, len(data))
for _, v := range data {
if f(v) {
out = append(out, v)
}
}
return out
}
func Map[T, U any](data []T, f func(T) U) []U {
out := make([]U, 0, len(data))
for _, v := range data {
out = append(out, f(v))
}
return out
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
labels := Map(evens, func(n int) string { return strconv.Itoa(n * n) })
fmt.Println(strings.Join(labels, " "))
}
The filter keeps the even numbers and the map turns each of them into its square written as text. Joining the result produces one line with the values in the original order.
$ go run main.go 4 16 36 64 100
Source
Go generics tutorial
Go slices package - reference
In this article we have implemented filter and map operations in Go. We
filtered strings and structs, wrote generic versions for any type, used
slices.DeleteFunc for an in-place filter, mapped integers and
strings, changed the element type in a map and combined both operations.
Author
List all Go tutorials.