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&#123; ~int | ~float64 &#125; 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.