ProgrammingBackend Developer

How are generics implemented in Go? What are the limitations, syntax, pitfalls, and suitable use cases?

Pass interviews with Hintsage AI assistant

Answer.

Generics were introduced in Go starting from version 1.18. For a long time, Go was considered a conservative language, where generics were excluded for the sake of simplicity, but with the growing number of projects and the development of the ecosystem, there arose a need for writing universal functions and structures. This is especially critical for container structures, collection processing algorithms, and infrastructure code.

Problem: Before the introduction of generics, it was necessary to duplicate code or use empty interfaces (interface{}), which led to a loss of type safety and reduced performance, as well as complicating debugging.

Solution: Generics are implemented in Go using type parameters, which are specified in square brackets for functions and types. With constraints, it is possible to restrict the allowable type parameters. This allows writing generic functions without losing type safety.

Example code:

package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }

Key features:

  • Type safety — elimination of type errors at compile time.
  • Constraints — the ability to limit generics to types that implement certain interfaces or support comparison.
  • Compatibility — novice developers can use generics as needed without restructuring projects immediately.

Tricky questions.

Can arithmetic operations (+, -, *, /) be used for any type parameter T in generics?

No. The Go compiler does not know whether the type parameter supports arithmetic. To do this, it is necessary to specify a constraint, for example, an interface with operator constraints starting from Go 1.18+.

Example code:

type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }

Can generic containers contain methods with different behavior for different types?

No. Methods of generic structures or functions are the same for all types unless using type switch inside the method. Behavior should be defined through constraints or purely parameterized.

Can types with type parameters (generic types) be created at the package level, not just for functions?

Yes, starting from Go 1.18, it is possible to create both generic functions and generic struct types:

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

Common mistakes and anti-patterns

  • Using interface{} instead of generics in new versions of Go.
  • Forgetting to specify constraints, leading to the inability to use operations within generic code.
  • Excessive generalization of simple functions (generics for the sake of generalization).

Real-life example

Negative case

Inside a library for working with collections, universal Map functionality was implemented through interface{}:

Pros:

  • Universal, can be used for any types.

Cons:

  • Lack of type safety, need for manual type casting, errors at runtime.

Positive case

In the same project, they switched to generics and set constraints through interfaces:

Pros:

  • Type safety, errors identified at compile time, simplified maintenance.

Cons:

  • Requires knowledge of new syntax, some IDEs poorly support complex constraints.