Go Generics

Go 1.18 added type parameters, letting you write functions and types that work over many types while staying fully type-checked. Before generics, a reusable Map or Filter over slices meant either duplicating code per type or using interface{} and losing type safety. Now you write it once as func Map[T, U any](s []T, f func(T) U) []U.

Go's generics are deliberately modest compared to TypeScript's or Rust's. There's no specialization and no type-level computation, and methods can't declare their own type parameters. That matches the language's philosophy: generics are for containers, algorithms, and utility code, not for building type-level puzzles.

TL;DR

Quick Example

Core Concepts

Type Parameters

Functions and types can declare type parameters:

Methods on generic types use the type's parameters (func (s Set[T]) Add(v T)), but a method can't introduce new* type parameters. Write a top-level function instead.

Constraints Are Interfaces

A constraint is an interface that describes the permitted types:

The operations you can use on a T are exactly those supported by every type in its constraint.

The Tilde ~

int matches only int. ~int matches int and any type defined as type UserID int. Without ~, generic numeric helpers would reject your domain types. The standard cmp.Ordered uses ~ throughout.

Built-In Constraints

Type Inference

Go infers type arguments from function arguments, and from assignment context in newer versions. You specify them explicitly only when they can't be deduced, typically when a type parameter appears only in the result: NewSet[string]() with no arguments.

The Generic Standard Library

When to Use Generics

Good fits:

Poor fits:

A useful rule from the Go team: write code, not types. Start concrete, and introduce type parameters when you find yourself writing the same code a second or third time.

Best Practices

Prefer the Standard Library

Before writing a generic helper, check slices, maps, and cmp. They're optimized, well-tested, and familiar to every Go developer.

Use the Loosest Constraint That Works

any if you only store and move values, comparable if you need == or map keys, cmp.Ordered if you sort or compare. Tighter constraints restrict callers for no gain.

Keep Signatures Readable

One or two type parameters with clear names (K, V, T, E) are idiomatic. If a signature needs four constrained parameters, the design is probably fighting the language.

Common Mistakes

Replacing Interfaces With Generics

Generics help when you need the concrete type preserved, for example returning []T rather than []fmt.Stringer.

Forgetting ~ in Numeric Constraints

interface{ int | float64 } rejects type Price float64. Use ~float64, or cmp.Ordered when ordering is all you need.

Using a Type-Set Interface as a Regular Type

Interfaces with type sets can only be used as constraints, not as variable or parameter types.

FAQ

Why did Go take so long to add generics?

The Go team wanted a design that kept compile times fast, code readable, and the language simple. After several rejected proposals, the type-parameters design with interface-based constraints shipped in Go 1.18 (2022). The deliberately limited scope was the point.

Do generics make Go code slower?

Usually not noticeably. Go compiles generics using "GC shape stenciling": types with the same memory shape share one instantiation, with a dictionary for type-specific operations. Pointer-heavy generic code can be marginally slower than hand-specialized code. Benchmark hot paths if it matters.

Can methods have type parameters?

No. Only functions and types can declare them; methods can use their receiver type's parameters. The workaround is a top-level generic function that takes the value as a parameter.

What's the difference between any and comparable?

any allows every type but permits no operations except assignment and passing around. comparable restricts to types that support ==, which excludes slices, maps, and functions, and is required for using T as a map key.

Related Topics

References