Generics
Go added generics in 1.18. Type parameters let you write functions and types that work with multiple types while keeping full type safety.
Type Parameters
func Map[T any, U any](s []T, f func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = f(v)
}
return result
}
// Usage - types are inferred
names := Map(users, func(u User) string { return u.Name })
lengths := Map(names, func(s string) int { return len(s) })
The [T any, U any] syntax declares type parameters. any means no constraint (any type is accepted).
Constraints
Constraints limit which types can be used as type arguments.
Built-in Constraints
import "cmp"
// comparable: types that support == and !=
func Contains[T comparable](s []T, target T) bool {
for _, v := range s {
if v == target {
return true
}
}
return false
}
// cmp.Ordered: types that support <, >, <=, >=
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
Custom Constraints
Define constraints as interfaces:
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
The ~ prefix means “underlying type”. ~int matches int and any named type whose underlying type is int (like type UserID int).
Method Constraints
type Stringer interface {
String() string
}
func JoinStrings[T Stringer](items []T, sep string) string {
parts := make([]string, len(items))
for i, item := range items {
parts[i] = item.String()
}
return strings.Join(parts, sep)
}
Generic Types
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) {
s.items = append(s.items, v)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
v := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return v, true
}
// Usage
s := &Stack[int]{}
s.Push(1)
s.Push(2)
v, _ := s.Pop() // 2
Self-Referential Generic Types (Go 1.26)
Go 1.26 allows types to refer to themselves in generic constraints, enabling patterns like fluent builders:
type Builder[Self any] interface {
SetName(string) Self
}
type UserBuilder struct {
name string
email string
}
func (b UserBuilder) SetName(n string) UserBuilder {
b.name = n
return b
}
Core Types Removed (Go 1.25)
Go 1.25 removed the “core type” restriction that previously blocked many useful generic patterns. Interfaces with multiple terms now work in more places:
// This was not allowed before Go 1.25
type Addable interface {
~int | ~float64 | ~string
}
func Add[T Addable](a, b T) T {
return a + b // works for int, float64, and string concatenation
}
The slices and maps Packages
The standard library includes generic utility packages:
import "slices"
nums := []int{3, 1, 4, 1, 5, 9}
slices.Sort(nums) // [1, 1, 3, 4, 5, 9]
slices.Contains(nums, 4) // true
slices.Index(nums, 5) // 4
idx, found := slices.BinarySearch(nums, 4)
sorted := slices.SortedFunc(slices.Values(nums), func(a, b int) int {
return b - a // descending
})
import "maps"
m := map[string]int{"a": 1, "b": 2, "c": 3}
keys := slices.Collect(maps.Keys(m)) // ["a", "b", "c"] (unordered)
values := slices.Collect(maps.Values(m)) // [1, 2, 3] (unordered)
clone := maps.Clone(m)
When to Use Generics
Good uses:
- Data structures (stacks, queues, trees, sets)
- Utility functions (map, filter, reduce, contains)
- Type-safe collections
- Algorithms that work on ordered/comparable types
Avoid generics when:
- An interface would work just as well (prefer
io.ReaderoverTwith a Read constraint) - You only use one or two concrete types (just write two functions)
- The generic version is harder to read than the concrete version
Tip: If you are writing
[T any]and never constraining T, ask yourself ifany(the non-generic version) would work just as well.
Exercises
-
Generic Set: Implement
Set[T comparable]withAdd,Contains,Remove,Len, andValuesmethods. -
Result type: Implement
Result[T any]that holds either a value or an error, withOk(T),Err(error),Unwrap() (T, error), andMap(func(T) U) Result[U]methods. -
Pipeline: Write a
Pipeline[T any]that chains transformation functionsfunc(T) Tand applies them in order.
Next: Concurrency Patterns | Interfaces