Go: Go Generics

Last updated: 2026-08-26

Go 1.18 introduced generics—allowing functions and data structures to be written using type parameters that work for int, سلسلة, and custom types, thereby completely eliminating the need for interface{} type assertions.

When you need to write three nearly identical sorting functions for int, سلسلة, and float64, generics allow you to write just one.

1. You will learn



2. A True Story of an Algorithm Engineer

(1) Pain point: Writing a sort دالة for each type

Charlie needs to implement a generic sorting library that supports three types: int, float64, and سلسلة:

"There were no generics in Go prior to version 1.18. I wrote three identical functions—the only difference was the type. Every time I added a new type, I had to copy and paste the code. The maintenance overhead was through the roof."

GO
// Bad code: no generics, copy-paste for each type
func SortInts(slice []int) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortFloat64s(slice []float64) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortStrings(slice []string) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
// Every new type is Ctrl+C / Ctrl+V

(2) Solution in Go 1.18: Generics

GO
// Good code: one دالة supports all ordered types
func Sort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

// Usage: automatic type inference
ints := []int{3, 1, 2}
Sort(ints)

floats := []float64{3.14, 1.41, 2.72}
Sort(floats)

strs := []سلسلة{"c", "a", "b"}
Sort(strs)
// No need for three functions!

(3) Performance: Before Generics vs. After Generics

Dimension interface{} + type assertion generics
Amount of code One copy per type One copy
Type Safety ❌ Runtime panic ✅ Compile-time check
Performance Involves boxing/unboxing overhead ✅ Zero overhead
Readability Numerous type assertions ✅ Clear


3. Generic Functions

▶ Example: Basic Generic Functions

⚙️ Prerequisite: Run go get golang.org/x/exp/constraints

GO
package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// Generic function: T is the type parameter, any is the constraint (all types)
func Print[T any](value T) {
    fmt.Println(value)
}

// Multiple type parameters
func Pair[A, B any](a A, b B) (A, B) {
    return a, b
}

// Constrained to ordered types
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    // Explicitly specify type parameters
    Print[int](42)
    Print[string]("hello")

    // Type inference (compiler automatically infers T)
    Print(42)       // T = int
    Print("hello")  // T = string

    fmt.Println(Max(3, 5))           // T = int → 5
    fmt.Println(Max(3.14, 2.72))    // T = float64 → 3.14
    fmt.Println(Max("apple", "banana")) // T = string → "banana"

    a, b := Pair(1, "one")
    fmt.Printf("A=%v (type: %T), B=%v (type: %T)\n", a, a, b, b)
}
▶ Try it Yourself

(2) Generic Function Syntax

GO
// Syntax: func Name[TypeParameter Constraint](paramList) returnType
func Name[T Constraint](param T) T { ... }

// Type parameter list is enclosed in [] (not angle brackets)
// Constraints can be any / comparable / custom interface
// Return values can use type parameters


4. Type Constraints

▶ Example: Built-in Constraints

GO
package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// any: all types (equivalent to interface{})
func Identity[T any](value T) T {
    return value
}

// comparable: types that support == and !=
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// constraints.Ordered: types that support < <= > >=
func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Contains([]int{1, 2, 3}, 2))         // true
    fmt.Println(Contains([]سلسلة{"a", "b", "c"}, "d")) // false
    fmt.Println(Min(10, 20))                            // 10
}
▶ Try it Yourself

▶ Example: Custom Constraints

GO
package main

import "fmt"

// Custom constraint: interface + type set
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
        ~float32 | ~float64
}

// ~int means all types whose underlying type is int (including type MyInt int)
// int only matches int itself

type Price float64

func Sum[T Numeric](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

func main() {
    ints := []int{1, 2, 3, 4, 5}
    fmt.Printf("Sum(ints) = %d\n", Sum(ints)) // 15

    floats := []float64{1.5, 2.5, 3.0}
    fmt.Printf("Sum(floats) = %.1f\n", Sum(floats)) // 7.0

    prices := []Price{10.99, 20.99, 5.00}
    fmt.Printf("Sum(prices) = %.2f\n", Sum(prices)) // 36.98 (underlying type float64)
}
▶ Try it Yourself

(3) Constraint Levels

constraint Supported Operations Source
any All operations (unconstrained) Built-in
comparable == != Built-in
constraints.Ordered < <= > >= golang.org/x/exp
constraints.Integer All integer types golang.org/x/exp
constraints.Float All floating-point types golang.org/x/exp
Custom Union Type interface { ~int | ~سلسلة }
💡 Tip: any is equivalent to interface{}, and comparable is a built-in constraint (no import required). constraints.Ordered, constraints.Integer, and others are located in the golang.org/x/exp/constraints package—this is experimental but has become a de facto standard. Starting with Go 1.21+, some constraints have been moved into the standard library.



5. Generic Data Structures

▶ Example: generic Stack

GO 📖 Display only
package main

import "fmt"

// Stack — generic stack
type Stack[T any] struct {
    items []T
}

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

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func (s *Stack[T]) Peek() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    return s.items[len(s.items)-1], true
}

func (s *Stack[T]) IsEmpty() bool {
    return len(s.items) == 0
}

func (s *Stack[T]) Size() int {
    return len(s.items)
}

func main() {
    // int stack
    intStack := Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    intStack.Push(3)

    for !intStack.IsEmpty() {
        if val, ok := intStack.Pop(); ok {
            fmt.Printf("Popped: %d\n", val)
        }
    }

    // string stack
    strStack := Stack[string]{}
    strStack.Push("hello")
    strStack.Push("world")
    fmt.Printf("Peek: %s\n", strStack.Peek()) // world
}
45 logic lines (exceeds 40-line limit, display only)

▶ Example: Generic Set

GO 📖 Display only
package main

import "fmt"

// Set — generic collection (comparable constraint)
type Set[T comparable] struct {
    items map[T]struct{}
}

func NewSet[T comparable]() *Set[T] {
    return &Set[T]{items: make(map[T]struct{})}
}

func (s *Set[T]) Add(item T) {
    s.items[item] = struct{}{}
}

func (s *Set[T]) Remove(item T) {
    delete(s.items, item)
}

func (s *Set[T]) Contains(item T) bool {
    _, ok := s.items[item]
    return ok
}

func (s *Set[T]) Size() int {
    return len(s.items)
}

func (s *Set[T]) Items() []T {
    result := make([]T, 0, len(s.items))
    for item := range s.items {
        result = append(result, item)
    }
    return result
}

// Union — set union (طريقة receivers cannot have additional type parameters)
func Union[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        result.Add(item)
    }
    for _, item := range b.Items() {
        result.Add(item)
    }
    return result
}

// Intersection — set intersection
func Intersection[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        if b.Contains(item) {
            result.Add(item)
        }
    }
    return result
}

func main() {
    set1 := NewSet[int]()
    set1.Add(1)
    set1.Add(2)
    set1.Add(3)

    set2 := NewSet[int]()
    set2.Add(3)
    set2.Add(4)
    set2.Add(5)

    fmt.Println("Set1:", set1.Items())
    fmt.Println("Set2:", set2.Items())
    fmt.Println("Union:", Union(set1, set2).Items())
    fmt.Println("Intersection:", Intersection(set1, set2).Items())
}
61 logic lines (exceeds 40-line limit, display only)
🔥 Common Mistake: Generic struct methods cannot declare additional type parameters—only the struct's own type parameters are available. If additional type parameters are needed, use a function (e.g., Union[T comparable](a, b *Set[T])). Additionally, generic types cannot be used directly in const declarations.



6. Type Inference and Instantiation

▶ Example: Type Inference

GO
package main

import (
    "fmt"
    "strconv"
)

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

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

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

func main() {
    // Type inference: T=int, U=string
    nums := []int{1, 2, 3, 4, 5}
    strs := Map(nums, strconv.Itoa)
    fmt.Println(strs) // ["1", "2", "3", "4", "5"]

    // Explicitly specify type parameters (when inference fails)
    explicit := Map[int, string](nums, strconv.Itoa)
    fmt.Println(explicit)

    // ❌ Type parameters can only be used for function params/return values, not variables
    // var list List[int]  ← type instantiation

    // Type instantiation: create a concrete type from a generic
    var intStack Stack[int]
    intStack.Push(10)
}
▶ Try it Yourself
100%
graph TB
    A[Generic function definition<br/>func Max[T Ordered](a, b T) T] --> B{Call Max(3, 5)}
    B --> C[Compiler infers T = int]
    C --> D[Instantiate Max[int]]
    D --> E[int version: func Max(a, b int) int]
    B --> F{Call Max(3.14, 2.72)}
    F --> G[Compiler infers T = float64]
    G --> H[Instantiate Max[float64]]
    H --> I[float64 version: func Max(a, b float64) float64]

(2) Generics vs. interface{}

Comparison interface{} + type assertion generics
Type Safety ❌ Runtime panic ✅ Compile-time check
Performance Boxing/unboxing (escape to heap) ✅ Zero overhead (compile-time expansion)
Amount of code One copy per type ✅ One copy of generic code
Flexibility Can store different types in the same slice ✅ Type is determined at compile time
Complexity Easy to understand ⚠️ Complex syntax


7. Complete Example: Generic Sorting Library

▶ Example: Full Implementation

GO 📖 Display only
// generic_sort.go
package main

import (
    "fmt"
    "sort"
    "golang.org/x/exp/constraints"
)

// ---------- Sorting functions ----------

// SortSlice sorts a slice of any ordered type
func SortSlice[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] < slice[j]
    })
}

// ReverseSort sorts in descending order
func ReverseSort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] > slice[j]
    })
}

// ---------- Search functions ----------

// BinarySearch performs binary search (requires sorted input)
func BinarySearch[T constraints.Ordered](slice []T, target T) (int, bool) {
    low, high := 0, len(slice)-1
    for low <= high {
        mid := low + (high-low)/2
        if slice[mid] == target {
            return mid, true
        } else if slice[mid] < target {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return -1, false
}

// ---------- Aggregation functions ----------

// Filter filters elements
func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce aggregates elements
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

type Person struct {
    Name string
    Age  int
}

func main() {
    // 1. Sort integers
    ints := []int{5, 2, 8, 1, 9, 3}
    SortSlice(ints)
    fmt.Printf("Sorted ints: %v\n", ints)

    // 2. Sort in descending order
    ReverseSort(ints)
    fmt.Printf("Reverse: %v\n", ints)

    // 3. Sort strings
    strs := []string{"banana", "apple", "cherry", "date"}
    SortSlice(strs)
    fmt.Printf("Sorted strings: %v\n", strs)

    // 4. Binary search
    idx, found := BinarySearch(ints, 5)
    fmt.Printf("BinarySearch 5: idx=%d, found=%v\n", idx, found)

    // 5. Filter
    evens := Filter(ints, func(n int) bool { return n%2 == 0 })
    fmt.Printf("Evens: %v\n", evens)

    // 6. Reduce
    sum := Reduce(ints, 0, func(acc, n int) int { return acc + n })
    fmt.Printf("Sum: %d\n", sum)

    // 7. Custom type (Person doesn't implement Ordered, can't sort directly)
    // Need a custom sort function
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35},
    }

    // Use a closure for custom sorting
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age
    })
    fmt.Printf("Sorted by age: %v\n", people)
}
75 logic lines (exceeds 40-line limit, display only)
💡 Tip: sort.Slice itself is not a generic function—it accepts any slices ([]any) and works via reflection. However, when used with closures, it integrates well with generics. True generic sorting comes from slices.Sort (golang.org/x/exp/slices), a new feature introduced in Go 1.21, which is implemented entirely using generics.


❓ FAQ

Q When were generics introduced?
A Go 1.18 (released in March 2022). The Go team spent ten years designing a generics solution and ultimately chose the "type parameters" approach, rather than the C++ template-based or Java erasure-based approaches. It is expanded at compile time, with zero runtime overhead.
Q What is the syntax for type parameters?
A func Name[T Constraint](param T) T. Type parameters are declared using square brackets [] (not angle brackets). Multiple parameters are allowed: func Map[T, U any](input []T, fn func(T) U) []U.
Q How do you write constraints?
A interface { set of types }. Built-in constraints: any (any type), comparable (comparable). Third-party: constraints.Ordered (ordered). Custom: interface { ~int | ~string }.
Q What is the difference between any and interface{}?
A any is an alias for interface{} (type any = interface{}), and the two are completely equivalent. Go 1.18 introduced generics along with any as a type alias. It is recommended to use any in generic constraints and interface{} in regular code.
Q How does type inference work?
A The compiler automatically infers type parameters based on function arguments. For example, Max(3, 5) → infers T=int. If inference fails or you want to specify the type explicitly, you can write: Max[int](3, 5). Type parameters cannot be inferred from the return type—at least one parameter must involve a type parameter.
Q Can generics be used in methods?
A Methods cannot have additional type parameters—only struct type parameters can be used. Therefore, func (s *Stack[T]) Push(item T) is valid, but func (s *Stack[T]) Convert[U any]() U is invalid. If additional type parameters are needed, use a regular function instead of a method.
Q How does generic performance compare?
A Zero runtime overhead. Go generics generate specific implementations for each type parameter combination at compile time (monomorphization). Therefore, Stack[int] and Stack[string] are completely different types at runtime, with no boxing or unboxing. The only cost is a slight increase in compile time and larger binary size.

📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Write a generic function Find[T comparable](slice []T, target T) int that returns the index of target in slice; if target does not exist, return -1. Verify that it works for the three types: int, string, and float64.

  2. Advanced (Difficulty ⭐⭐): Implement a generic Queue[T any] (first-in, first-out queue). Requirements: (1) Enqueue, Dequeue, Peek, and IsEmpty methods; (2) Support for any type; (3) Implement a ring buffer to avoid frequent resizing; (4) Verify concurrent safety using -race.

  3. Challenge (Difficulty ⭐⭐⭐): Implement a generic concurrency-safe cache Cache[K comparable, V any]. Requirements: (1) Get/Set/Delete/Clear methods; (2) RWMutex protection; (3) TTL expiration mechanism; (4) Support for the OnEvicted callback (called when a key is deleted or expires); (5) Use generics to ensure that the key type must be comparable.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏