Generics in Go: why they are needed and how to use them for beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: what are Generics and why did they appear in Go?



For a long time, the Go (Golang) community managed without generics (generic programming). Developers used interfaces, empty interface{}, and code generation to work around the limitations of strict typing. In Go version 1.18 (February 2022), generics were finally added to the language — a powerful tool that allows you to write functions and structures that work with different data types without losing type safety.



In this article, we will break down why Generics are needed in Go, how they work, and learn how to apply them in practice. The material is designed for beginner developers who are already familiar with the basics of Go: variables, functions, and slices.



Why are generics needed in Go?



Imagine you need to write a function that finds the minimum value in a slice of numbers. Without generics, you would have to write separate functions for int, float64, and other types:



// Without generics — three identical functionsfunc MinInt(a, b int) int {    if a < b {        return a    }    return b}

func MinFloat64(a, b float64) float64 { if a < b { return a } return b}


This leads to code duplication. Previously, this problem was solved using interface{} and type casting, but this approach lost compile-time safety:



func Min(a, b interface{}) interface{} {    // Comparison and casting — easy to make mistakes    return a}


Generics solve three key problems:

  • Type safety — type errors are caught at compile time.
  • Eliminating duplication — one function works with any types.
  • Code readability — explicit indication of which types are supported.


Syntax basics: type parameters and constraints



Generics in Go are built on two concepts: type parameters and constraints.



Type parameters



Type parameters are specified in square brackets after the function or type name. By convention, the letter T (for Type) is most often used, but any names can be used:



// Function with one type parameter Tfunc Print[T any](value T) {    fmt.Println(value)}

// UsagePrint[int](42) // explicitly specify the typePrint("Hello") // compiler infers the type automatically


The keyword any is an alias for the empty interface interface{}. It means the function accepts any type.



Constraints



Constraints define which types can be used with a generic. For example, to compare numbers, the type needs to support the < and > operators. Built-in constraints for numeric types are in the golang.org/x/exp/constraints package:



import "golang.org/x/exp/constraints"

// Constraint — only integersfunc Sum[T constraints.Integer](numbers []T) T { var total T for _, n := range numbers { total += n } return total}


You can also create your own constraints using interfaces:



// Custom constraint: the type must have a String() methodtype Stringer interface {    String() string}

func Describe[T Stringer](value T) string { return "Value: " + value.String()}


Practical examples of using Generics



Let's look at several real-world scenarios where generics significantly simplify code.



1. Universal search function in a slice



// Finds the index of an element in a slice. -1 if not found.func Index[T comparable](slice []T, target T                

Blogs

Book Recommendations