Generic Constraints & Type Sets
Generics let you write one function or type that works for many element types using type parameters like [T any] , while constraints — interfaces that list a set of permitted types — control which types are allowed and which operations the compiler will let you use on them.
Learn Generic Constraints & Type Sets in our free Go course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Generic functions: Map, Filter, Reduce
Type parameters go in square brackets after the name. Map[T, U any] turns a []T into a []U ; any is the constraint that allows every type. At the call site you usually write nothing extra — inference reads the types straight from the arguments.
2️⃣ Constraints & type sets ( ~int | ~float64 )
A constraint is an interface that lists a type set . interface{ ~int | ~float64 } permits those types and lets you use + , - , and < on the value. The ~ admits named types built on those (so Celsius works). The built-in comparable permits == and != .
3️⃣ Generic types and an Ordered constraint
Types can be generic too: Stack[T any] is one definition that works for any element type, and its methods reference T . For Max we need the < operator, so we declare our own Ordered constraint (the standard library keeps the official one in an experimental module).
🎯 Your Turn
Make a tiny generic First helper. Fill in the two blanks marked ___ , then run it.
❌ Using + or < on a [T any] — "operator not defined".
✅ Constrain T to a type set that permits the operator (e.g. Number , Ordered ).
❌ Writing int instead of ~int — named types like type ID int are rejected.
❌ Inference fails when a type parameter appears only in the return type.
❌ Using == on a [T any] — not all types are comparable (slices, maps, funcs aren't).
~int accepts ID (its underlying type is int ). Plain int matches only the exact int type.
comparable — the built-in constraint for types you can compare with == / != , exactly what map keys require.
Write Min[T Number] returning the smallest element, then call it on a slice of ints and a slice of float64s.
Practice quiz
Where do type parameters go in a generic function?
- in parentheses before the name
- after the return type
- in square brackets after the name
- in a separate file
Answer: in square brackets after the name. Type parameters are written in square brackets after the name, e.g. Map[T, U any](...).
What is a constraint in Go generics?
- an interface that limits which types T accepts
- a runtime check
- a struct tag
- a panic guard
Answer: an interface that limits which types T accepts. A constraint is an interface listing the permitted type set and allowed operations.
What does the constraint any allow?
- only numeric types
- only comparable types
- nothing
- every type, but only assignment (no + < ==)
Answer: every type, but only assignment (no + < ==). any permits all types but guarantees no operations beyond moving values around.
What does the ~ in ~int mean?
- not int
- int or any type whose underlying type is int
- a pointer to int
- approximately int
Answer: int or any type whose underlying type is int. ~int admits named types like type Age int whose underlying type is int.
Which built-in constraint permits == and !=?
- comparable
- any
- Ordered
- Number
Answer: comparable. comparable allows == and != — exactly what map keys need.
Does func Eq[T any](a, b T) bool { return a == b } compile?
- yes
- only for ints
- no — any doesn't guarantee ==
- only at runtime
Answer: no — any doesn't guarantee ==. any can't use ==; the compiler reports incomparable types. Use comparable instead.
What does var total T give inside a generic function?
- nil
- the zero value of T
- a compile error
- a random value
Answer: the zero value of T. var total T is the zero value of whatever concrete type T becomes.
How does Sum(nums) usually know T without [int]?
- a default of int
- reflection at runtime
- it cannot
- type inference from the arguments
Answer: type inference from the arguments. Inference reads the type parameters straight from the ordinary arguments.
When does type inference fail?
- always for two parameters
- when a type parameter appears only in the return type
- for slices
- for strings
Answer: when a type parameter appears only in the return type. If T only shows up in the return type (not in any argument), you must specify it explicitly.
Can a type be generic, like Stack[T any]?
- no, only functions
- only built-in types
- yes, types can take type parameters too
- only with comparable
Answer: yes, types can take type parameters too. Types can be generic; Stack[T any] is one definition that works for any element type.