# Go Generics in 2026: Type Parameters, Constraints and Interview Questions > Master Go generics for technical interviews with questions on type parameters, constraints, the tilde operator, and practical implementations like generic caches. - Published: 2026-07-08 - Updated: 2026-07-08 - Author: SharpSkill - Reading time: 9 min --- Go 1.18 introduced generics to the language in March 2022, and since then, the feature has matured through Go 1.21, 1.22, and beyond. This article covers essential Go generics interview questions for 2026, from basic type parameters to advanced constraint patterns that hiring managers commonly ask about. > **What Are Go Generics?** > > Go generics enable writing functions and types that work with any data type while maintaining compile-time type safety. Unlike interfaces that use runtime type assertions, generics resolve types at compile time, providing better performance and earlier error detection. ## Understanding Type Parameters in Go Type parameters form the foundation of Go generics. A type parameter is a placeholder for a type that gets specified when the generic function or type is used. ```go // generic.go // Basic generic function with type parameter T func PrintSlice[T any](items []T) { for _, item := range items { fmt.Println(item) } } // Usage - type inference determines T automatically func main() { PrintSlice([]int{1, 2, 3}) // T is int PrintSlice([]string{"a", "b"}) // T is string } ``` The square bracket syntax `[T any]` declares a type parameter `T` constrained by `any`. Go's compiler infers the concrete type from the arguments, eliminating the need for explicit type specification in most cases. ## Common Interview Question: What Is the Difference Between `any` and `comparable`? Interviewers frequently ask about the built-in constraints `any` and `comparable`. Understanding their differences demonstrates solid generics knowledge. ```go // constraints.go // any: accepts all types (alias for interface{}) func Process[T any](value T) T { return value } // comparable: only types that support == and != func Contains[T comparable](slice []T, target T) bool { for _, v := range slice { if v == target { // This comparison requires comparable return true } } return false } // This compiles Contains([]int{1, 2, 3}, 2) // This fails: slices are not comparable // Contains([][]int{{1}, {2}}, []int{1}) ``` The `comparable` constraint restricts type parameters to types that support equality operators. Slices, maps, and functions are excluded because Go does not define equality for them at the language level. ## Creating Custom Type Constraints with Interfaces Go uses interfaces to define custom constraints, expanding what generic functions can accept. The [Go specification](https://go.dev/ref/spec#Type_constraints) defines how type elements work within constraint interfaces. ```go // number.go // Custom constraint using type union type Number interface { int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64 } // Generic function constrained to numeric types func Sum[T Number](values []T) T { var total T for _, v := range values { total += v // + operator works because all Number types support it } return total } func main() { fmt.Println(Sum([]int{1, 2, 3})) // 6 fmt.Println(Sum([]float64{1.5, 2.5})) // 4.0 } ``` Type unions with `|` specify which exact types satisfy the constraint. This approach provides more control than `any` while avoiding runtime type assertions. ## Interview Question: Explain the `~` Tilde Operator in Constraints The tilde operator `~` in constraints matches both a type and all types with that underlying type. This question tests understanding of Go's type system. ```go // underlying.go // ~int matches int and any type with int as underlying type type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } // Custom type with int as underlying type type UserID int type Temperature int64 func Abs[T Signed](value T) T { if value < 0 { return -value } return value } func main() { var id UserID = -42 var temp Temperature = -10 fmt.Println(Abs(id)) // 42 - works because ~int matches UserID fmt.Println(Abs(temp)) // 10 - works because ~int64 matches Temperature } ``` Without `~`, the constraint `int` would only match the exact type `int`, not custom types like `UserID`. The tilde expands the constraint to include derived types, making generic code more flexible. ## Generic Types: Structs and Methods Generic types extend beyond functions to structs and methods. This pattern appears frequently in data structure implementations. ```go // stack.go // Generic Stack type type Stack[T any] struct { items []T } // Push adds an element to the stack func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) } // Pop removes and returns the top element func (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T // Zero value for type T return zero, false } index := len(s.items) - 1 item := s.items[index] s.items = s.items[:index] return item, true } func main() { intStack := Stack[int]{} intStack.Push(10) intStack.Push(20) val, ok := intStack.Pop() // val=20, ok=true } ``` Note that methods on generic types must repeat the type parameter in brackets but cannot introduce new type parameters. The receiver `(s *Stack[T])` binds the method to the specific instantiation of `Stack`. ## Interview Question: Why Can't Methods Have Their Own Type Parameters? Go prohibits additional type parameters on methods, a design decision that surprises developers coming from languages like Java or C#. The [Go generics proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md) explains this limitation exists to keep the type system tractable. ```go // This is NOT valid Go code type Container[T any] struct { value T } // ERROR: methods cannot have type parameters // func (c *Container[T]) Transform[U any](fn func(T) U) U { // return fn(c.value) // } // Valid alternative: use a standalone function func Transform[T, U any](c *Container[T], fn func(T) U) U { return fn(c.value) } ``` The workaround uses top-level functions with multiple type parameters instead of methods. This design keeps method dispatch simple and avoids complex interactions between receiver types and method type parameters. ## Type Inference and Constraint Inference Go's type inference reduces verbosity when calling generic functions. Understanding when explicit type arguments are required helps write cleaner code. ```go // inference.go func Map[T, R any](input []T, transform func(T) R) []R { result := make([]R, len(input)) for i, v := range input { result[i] = transform(v) } return result } func main() { numbers := []int{1, 2, 3} // Type inference: T=int, R=string inferred from arguments strings := Map(numbers, func(n int) string { return fmt.Sprintf("%d", n) }) // Explicit types sometimes required for complex cases // Map[int, string](numbers, converter) } ``` Type inference works from function arguments to type parameters. When the compiler cannot infer types from arguments alone, explicit type arguments in square brackets resolve the ambiguity. ## Constraints Package: `cmp` and `slices` in the Standard Library Go 1.21 added the `cmp` package with the `Ordered` constraint and comparison functions. The `slices` package demonstrates idiomatic generic code in the standard library. ```go // stdlib.go import ( "cmp" "slices" ) func main() { numbers := []int{3, 1, 4, 1, 5, 9} // slices.Sort uses cmp.Ordered constraint internally slices.Sort(numbers) // [1, 1, 3, 4, 5, 9] // Binary search on sorted slice index, found := slices.BinarySearch(numbers, 4) // cmp.Compare returns -1, 0, or 1 result := cmp.Compare(3, 5) // -1 // cmp.Or returns first non-zero value value := cmp.Or(0, 0, 42, 100) // 42 } ``` The [standard library documentation](https://pkg.go.dev/cmp) shows how these packages leverage generics for type-safe operations on ordered types. Familiarity with these packages demonstrates practical generics knowledge beyond theoretical understanding. ## Interview Question: Implementing a Generic Cache A common interview exercise asks candidates to implement a generic, thread-safe cache. This tests generics, concurrency with the [sync package](/technologies/go/interview-questions/sync-primitives), and API design. ```go // cache.go import ( "sync" "time" ) type Cache[K comparable, V any] struct { mu sync.RWMutex items map[K]cacheItem[V] } type cacheItem[V any] struct { value V expiration time.Time } func NewCache[K comparable, V any]() *Cache[K, V] { return &Cache[K, V]{ items: make(map[K]cacheItem[V]), } } func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) { c.mu.Lock() defer c.mu.Unlock() c.items[key] = cacheItem[V]{ value: value, expiration: time.Now().Add(ttl), } } func (c *Cache[K, V]) Get(key K) (V, bool) { c.mu.RLock() defer c.mu.RUnlock() item, exists := c.items[key] if !exists || time.Now().After(item.expiration) { var zero V return zero, false } return item.value, true } ``` This implementation uses `comparable` for keys (map requirement) and `any` for values. The separate `cacheItem` struct shows how generic types can nest within each other. Thread safety comes from `sync.RWMutex`, a pattern covered in Go [concurrency interviews](/technologies/go/interview-questions/concurrency-patterns). ## Zero Values and Type Constraints Handling zero values in generic code requires understanding how Go initializes variables of parameterized types. ```go // zero.go // Return zero value when slice is empty func First[T any](slice []T) T { if len(slice) == 0 { var zero T // Zero value: 0 for int, "" for string, nil for pointers return zero } return slice[0] } // Alternative: return pointer to avoid ambiguity func FirstOrNil[T any](slice []T) *T { if len(slice) == 0 { return nil } return &slice[0] } ``` The `var zero T` pattern creates a zero value for any type `T`. For types where zero is a valid value (like `0` for integers), the pointer-returning variant distinguishes between "not found" and "found zero." ## Advanced: Combining Multiple Constraints Complex generic functions may require types to satisfy multiple constraints. Go handles this through interface embedding. ```go // combined.go // Constraint requiring both ordering and string conversion type Stringable interface { String() string } type OrderedStringable interface { cmp.Ordered Stringable } // Alternative: use type parameters with multiple constraints inline func PrintSorted[T interface{ cmp.Ordered; fmt.Stringer }](items []T) { slices.Sort(items) for _, item := range items { fmt.Println(item.String()) } } ``` Interface embedding combines constraints, requiring types to implement all embedded interfaces. The inline syntax `interface{ A; B }` provides the same functionality without declaring a named constraint type. ## Conclusion - Type parameters with `[T any]` enable writing reusable, type-safe code without runtime reflection - `comparable` restricts to types supporting `==` and `!=`, required for map keys - The `~` tilde matches types with a specific underlying type, expanding constraint flexibility - Custom constraints use type unions (`int | string`) to specify exact allowed types - Methods cannot have their own type parameters; use top-level functions as an alternative - Standard library packages `cmp` and `slices` demonstrate idiomatic generic patterns - Zero values via `var zero T` handle empty or missing data in generic functions - Thread-safe generic data structures combine generics with sync primitives --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/go/go-generics-type-parameters-constraints-interview-questions