Generics Basics
10 examples to get you started with Generics - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Generics require Go 1.18+; stdlib
slices/maps/cmpexamples need Go 1.21+. - Create a module:
mkdir gdemo && cd gdemo && go mod init example.com/gdemo. - Save snippets as
main.goand run withgo run ..
Basic Examples
1. Your first generic function
A type parameter T with constraint cmp.Ordered enables < on numbers and strings.
package main
import (
"cmp"
"fmt"
)
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 7), Max("a", "z"))
}- Type parameters appear in square brackets before the parameter list.
cmp.Orderedis a stdlib constraint for ordered types.- The compiler infers
Tfrom arguments at the call site.
Related: Generics in Go: Constraints Without Templates - mental model for constraints
2. Explicit type arguments
Pass type arguments when inference is ambiguous.
package main
import "fmt"
func Zero[T any]() T {
var z T
return z
}
func main() {
fmt.Printf("%v %q\n", Zero[int](), Zero[string]())
}anyis the constraint that accepts all types.Zero[int]()substitutesTwithint.- Zero value of
Tis returned without reflection.
Related: Type Parameters & Constraint Interfaces -
anyand named constraints
3. Generic slice contains
Loop with index-free range over []T.
package main
import "fmt"
func Contains[T comparable](s []T, v T) bool {
for _, x := range s {
if x == v {
return true
}
}
return false
}
func main() {
fmt.Println(Contains([]string{"a", "b"}, "b"))
}comparableallows==and!=onT.- Slices of comparable elements are fine;
Titself cannot be a slice type in acomparablemap-key role. - Prefer
slices.Containsfrom stdlib in production code.
Related: Type Sets & the comparable Constraint - what comparable allows
4. Generic struct: stack
Type parameters on struct types carry through methods.
package main
import "fmt"
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
}
func main() {
var st Stack[int]
st.Push(1)
v, _ := st.Pop()
fmt.Println(v)
}- Methods on
Stack[T]use the sameTas the struct. - Each instantiation (
Stack[int],Stack[string]) is a distinct type. - Zero value
Stack[int]{}is ready to use.
Related: Generic Data Structures: Sets, Queues, Maps - reusable containers
5. Interface constraint with a method
Constraints can require methods, not only type unions.
package main
import "fmt"
type Stringer interface {
String() string
}
func Join[T Stringer](items []T, sep string) string {
if len(items) == 0 {
return ""
}
out := items[0].String()
for _, it := range items[1:] {
out += sep + it.String()
}
return out
}
type ID int
func (id ID) String() string { return fmt.Sprintf("id:%d", int(id)) }
func main() {
fmt.Println(Join([]ID{1, 2}, ","))
}T Stringermeans everyTmust implementString() string.- Interface satisfaction is implicit - no
implementskeyword. - Method calls on
Tare allowed because they are in the constraint.
Related: Type Parameters & Constraint Interfaces - method constraints
6. Map keys with comparable
Build a frequency table keyed by T.
package main
import "fmt"
func Count[T comparable](items []T) map[T]int {
m := make(map[T]int)
for _, v := range items {
m[v]++
}
return m
}
func main() {
fmt.Println(Count([]string{"a", "b", "a"}))
}- Map keys must be
comparable. - Struct keys work when all fields are comparable.
- Slices and maps cannot be map keys.
Related: Type Sets & the comparable Constraint - map key rules
7. Instantiate a generic type alias pattern
Short variable declaration picks the instantiation.
package main
import "fmt"
type Pair[A, B any] struct {
First A
Second B
}
func main() {
p := Pair[string, int]{"go", 1}
fmt.Println(p.First, p.Second)
}- Multiple type parameters are comma-separated:
[A, B any]. - Field types use the parameters directly.
- Inference on constructors is limited; explicit type args are common on types.
Related: Generic Data Structures: Sets, Queues, Maps - multi-parameter containers
Intermediate Examples
8. Underlying type constraint with ~
Accept named types based on their underlying type.
package main
import "fmt"
type Celsius float64
type Fahrenheit float64
type Number interface {
~float64
}
func Double[T Number](v T) T {
return v * 2
}
func main() {
fmt.Println(Double(Celsius(10)), Double(Fahrenheit(20)))
}~float64includesCelsiusandFahrenheitbecause their underlying type isfloat64.- Without
~, only the literal typefloat64would match. - Use named constraint types for reuse across functions.
Related: Type Parameters & Constraint Interfaces -
~operator depth
9. Union constraint for numeric min
Union types list allowed concrete types.
package main
import "fmt"
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
func Abs[T Integer](v T) T {
if v < 0 {
return -v
}
return v
}
func main() {
fmt.Println(Abs(int8(-3)), Abs(int64(-9)))
}|builds a union inside an interface constraint.- Operations in the body must be valid for every union member.
- Widen unions carefully - readability drops fast.
Related: Type Parameters & Constraint Interfaces - union limits
10. slices.Contains from stdlib
Reach for stdlib generic helpers before custom copies.
package main
import (
"fmt"
"slices"
)
func main() {
nums := []int{1, 2, 3}
fmt.Println(slices.Contains(nums, 2))
fmt.Println(slices.Index(nums, 9))
}slicesandmapspackages are generic-first APIs in Go 1.21+.- They are maintained with the runtime and escape analysis in mind.
- Custom generics still help for domain-specific containers and algorithms.
Related: Generic Algorithms: slices, maps & cmp Packages - stdlib catalog
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).