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
- Type parameters go in square brackets:
func Max[T cmp.Ordered](a, b T) T. - Constraints are interfaces. They can list methods, type sets (
int | float64), or both. ~Tmeans "any type whose underlying type is T", so it includes named types liketype Celsius float64.- Built-ins:
any(no constraint),comparable(supports==), andcmp.Ordered(supports<). - Arguments are usually inferred:
Max(3, 7)needs no[int]. - The standard
slices,maps, andcmppackages cover most everyday needs. Reach for your own generics when you'd otherwise duplicate code or useany.
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
slices:Sort,SortFunc,Contains,Index,BinarySearch,Max,Compact,Reverse,Chunk, and more.maps:Keys,Values(as iterators),Clone,Copy,DeleteFunc.cmp:Compare,Less,Or,Ordered.- Iterators (Go 1.23):
iter.Seq[T]andrange-over-func build lazy generic pipelines. - Built-in
minandmax(Go 1.21) work on any ordered type.
When to Use Generics
Good fits:
- Functions over slices, maps, and channels of any element type (
Map,Filter,Chunk,GroupBy). - General-purpose data structures: sets, caches, trees, heaps, queues. See data structures.
- Type-safe wrappers that previously used
any, such as a typedsync.Poolor a typed atomic value. - Functions whose logic is identical across types and differs only by type.
Poor fits:
- When an ordinary interface with methods expresses the behavior.
io.Readerdoesn't need to be generic. - When the implementation differs per type (use interfaces or a type switch).
- Single-use code with only one concrete type in sight.
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
- Go — The language overview
- Go Interfaces — Constraints are interfaces; runtime vs compile-time polymorphism
- TypeScript Generics — A more expressive take on the same idea
- Rust Traits — Trait bounds as generic constraints
- Type Systems — Parametric polymorphism in general
- Data Structures — Where generics shine