Skip to main content
Starting with version 1.18, Go added support for generics (also known as type parameters). This allows you to write functions and data structures that work with any type while maintaining type safety.

Generic functions

package main

import "fmt"

// SlicesIndex takes a slice of any comparable type and an element,
// returning the index of the first occurrence of v in s, or -1 if not present.
func SlicesIndex[S ~[]E, E comparable](s S, v E) int {
    for i := range s {
        if v == s[i] {
            return i
        }
    }
    return -1
}

func main() {
    var s = []string{"foo", "bar", "zoo"}

    // When invoking generic functions, we can often rely on type inference.
    // We don't have to specify the types for S and E - the compiler infers them.
    fmt.Println("index of zoo:", SlicesIndex(s, "zoo"))

    // Though we could also specify them explicitly.
    _ = SlicesIndex[[]string, string](s, "zoo")
}
Output:
index of zoo: 2
This SlicesIndex function is similar to the standard library’s slices.Index function.

Type parameters explained

func SlicesIndex[S ~[]E, E comparable](s S, v E) int
Let’s break down this signature:
Type parameter list. Defines two type parameters:
  • S - constrained to slice types (~[]E)
  • E - constrained to comparable types
The ~ means “underlying type”. S can be any type whose underlying type is []E, allowing custom slice types.
A built-in constraint meaning the type can be compared with == and !=. Includes booleans, numbers, strings, pointers, channels, and arrays/structs of comparable types.
For a thorough explanation of type parameter syntax, see this blog post.

Generic types

// List is a singly-linked list with values of any type.
type List[T any] struct {
    head, tail *element[T]
}

type element[T any] struct {
    next *element[T]
    val  T
}

// Methods on generic types keep the type parameters.
// The type is List[T], not List.
func (lst *List[T]) Push(v T) {
    if lst.tail == nil {
        lst.head = &element[T]{val: v}
        lst.tail = lst.head
    } else {
        lst.tail.next = &element[T]{val: v}
        lst.tail = lst.tail.next
    }
}

func (lst *List[T]) AllElements() []T {
    var elems []T
    for e := lst.head; e != nil; e = e.next {
        elems = append(elems, e.val)
    }
    return elems
}

func main() {
    lst := List[int]{}
    lst.Push(10)
    lst.Push(13)
    lst.Push(23)
    fmt.Println("list:", lst.AllElements())
}
Output:
list: [10 13 23]

Type constraints

func Print[T any](v T) {
    fmt.Println(v)
}
No restrictions. T can be any type.

Type inference

Go’s compiler can infer type arguments in most cases:
// Explicit type arguments
result := SlicesIndex[[]string, string]([]string{"a", "b"}, "b")

// Type inference - compiler figures it out
result := SlicesIndex([]string{"a", "b"}, "b")
Type inference makes generic code cleaner. You only need explicit type arguments in ambiguous cases.

Common patterns

Generic data structures

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

Generic algorithms

func Map[T, U any](slice []T, f func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}

Optional values

type Option[T any] struct {
    value *T
}

func Some[T any](v T) Option[T] {
    return Option[T]{value: &v}
}

Result types

type Result[T any] struct {
    value T
    err   error
}

Limitations

Generics in Go have some limitations:
  • No generic methods (only functions and types)
  • Type parameters can’t be used with method definitions inside a function
  • Some complex constraint scenarios aren’t supported

Best practices

Don’t use generics everywhere. Use them when you have multiple types doing the exact same thing. For single-use code, concrete types are clearer.
If you’re only using one or two types, concrete implementations are often clearer than generic ones.
Before adding generics, consider if an interface would solve your problem more simply.
For complex generics, use meaningful names like Key and Value instead of just K and V.

Interfaces

Traditional polymorphism in Go

Range over iterators

Use generics with iterators

slices package

Generic slice utilities

Build docs developers (and LLMs) love