Generics allow you to write functions that work with any type, while still being type-safe. The main aim of generics is to achieve greater flexibility in terms of writing code with the addition of fewer lines.

Generics in Golang?

  • Generics in Go (Golang) were introduced in Go 1.18 to let you write type-safe, reusable code without duplicating logic for every data type.
  • Type-safe means Go ensures that the wrong type of data is not used — meaning a compile-time error will occur if the types don’t match.
  • Reusable code means you don’t need to copy/paste the same function or structure for different types again and again.

Basic generics function example

  • Without generics We would have to write separate functions to add integers and floats.

    func SumInt(a, b int) int {
      return a + b
    }
    
    func SumFloat(a, b float64) float64 {
      return a + b
    }
    
  • With generics When we use generics then we define type parameters using square brackets [T]. With generics, we can perform these different operations using a single function.

    func Sum[T int | float64](a, b T) T {
      return a + b
    }
    
  • Usage

    fmt.Println(Sum(2, 3))        // int
    fmt.Println(Sum(2.5, 3.1))    // float64
    

What is type parameters?

  • These are placeholder types, meaning the type isn’t fixed until you actually use the function or data structure. It indicates: “This function or structure can work with any type, which you will decide when you use it.
  • T is a generic type parameter. We write it inside square brackets [] when we want to make a function or structure generic.
  • When you call the function, the Go compiler replaces T with the actual type.

    [T int | float64]
    
    • T = type parameter
    • int | float64 = type constraint
    • Means: T can only be int or float64

Generic function with any?

  • In Go, any is an alias for interface{}. interface{} means it can be any type.
  • So if you write [T any], it means T can hold a value of any type (int, string, bool, float64, …).

    func Print[T any](value T) {
      fmt.Println(value)
    }
    
  • Output will be like

    Print(10)      --> 10
    Print("hello") --> hello
    Print(true)    --> true
    

What are constraints in generics?

  • A constraint is a rule that specifies which types a generic type parameter T is allowed to use.

    type Number interface {
      int | int64 | float64
    }
    
  • Here, Number is a custom interface.
  • int | int64 | float64 means that if you use Number as a type parameter, only int, int64, or float64 are allowed.

Generic function using constraint

func Add[T Number](a, b T) T {
  return a + b
}
  • T Number = T can only be a Number type (int, int64, float64).
  • a, b T = the function inputs are of type T.
  • return a + b = this function will return the sum of both inputs.

Find maximum from slice elements using generics

  • There is a generic function that returns the maximum value from the elements of a slice.
  • [T int | float64] = this is a type constraint, meaning T can only be int or float64.

    func Max[T int | float64](values []T) T {
      max := values[0]
      for _, v := range values {
        if v > max {
          max = v
        }
      }
      return max
    }
    
  • We can use this function for both integers and floats.

    ints := []int{10, 5, 30, 2}
    floats := []float64{1.2, 3.4, 0.5, 2.8}
    
    fmt.Println(Max(ints))    // Output: 30
    fmt.Println(Max(floats))  // Output: 3.4
    

Conclusion

Generics in Go unlock the power of writing one function, one structure, yet handling countless types—making your code smarter, cleaner, and future-ready. Once you embrace them, you’ll never look back at duplicated logic the same way again.