Generics (Type Parameters)

Added in Go 1.18, generics let you write one function or type that works across many types — without losing Go's type safety. By the end you'll write generic functions with type parameters, constrain them with interfaces, and build a fully generic data structure.

Learn Generics (Type Parameters) in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.

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

Type inference usually lets you call a generic function without spelling out the type: Max(3, 7) instead of .

1️⃣ Generic Functions

A type parameter goes in square brackets after the function name: . Inside the function, T stands for whatever concrete type the caller uses. The list is a quick inline constraint saying which types are allowed — here, the ones > works on.

A function can have several type parameters. Map below takes an input type T and a possibly-different output type U — the staple of functional-style slice transforms.

Your turn. Fill in the constraint that allows any element type so First works on both slices.

2️⃣ Constraints

A constraint is just an interface that lists the permitted types (joined with | ) and/or required methods. Naming it makes intent clear and reusable. The ~ token matters: ~int means "int or any named type whose underlying type is int", so your own type Celsius int still qualifies.

3️⃣ Generic Types

Types can take type parameters too. is a stack of any element type, and its methods reuse the same T . To produce the zero value of an unknown type inside a method, declare var zero T — that's the idiomatic way to return "nothing" when the type isn't known until instantiation.

These lines define a generic Contains function and call it. Put them in the correct order:

B, D, C, E, A, F, G. Open the function with a comparable constraint (needed for == ), loop over the slice checking each element, close the loop, return false if nothing matched, close the function, then call it. comparable is required because the body uses == .

Predict the output before revealing the answer.

9 — Go infers T = int from the arguments, so no explicit [int] is needed, and 9 > 4.

No. any doesn't guarantee == works (slices, maps, and funcs aren't comparable). Change the constraint to comparable and it compiles.

0 true — var z T is the zero value of whatever T is: 0 for int , and the empty string for string (so the comparison to "" is true).

Q: Generics or interfaces — which should I use?

Use an interface when you want different types to share behaviour behind a contract. Use generics when you want the same logic to operate on many types while keeping the exact type (so the compiler still checks it). Containers and slice utilities are the classic generics case.

A built-in constraint for types that can be compared with == and != — most types, but not slices, maps, or functions. Use it for map keys and equality checks in generic code.

Without ~ , the constraint matches the exact type int only. With ~int , it also matches any type whose underlying type is int , such as type Age int . That makes constraints work with user-defined named types.

Generics are resolved at compile time, so there's no runtime type-checking cost like reflection. Go may share generated code across types in some cases, but for everyday use you can treat generics as zero-overhead abstractions.

No blanks — just a brief. Write a single Filter that works on any slice type, then use it on both ints and strings. Together with Map from earlier, you now have the building blocks of a functional toolkit.

Practice quiz

In which Go version were generics added?

  • 1.11
  • 1.16
  • 1.18
  • 1.21

Answer: 1.18. Generics arrived in Go 1.18.

What does [T any] declare?

  • a type parameter T allowing any type
  • a variable named T
  • an interface method
  • a constant

Answer: a type parameter T allowing any type. [T any] is a type parameter named T that accepts any type.

In func Map[T, U any](s []T, fn func(T) U) []U, T and U are...

  • always the same type
  • both int
  • return values
  • input and output types that can differ

Answer: input and output types that can differ. T is the input element type and U the output type; they may be different.

What does fmt.Println(Max(3, 7)) print for a generic Max?

  • 3
  • 7
  • 10
  • a compile error

Answer: 7. Go infers T = int and Max returns the larger, 7.

What does a constraint like ~int | ~int64 | ~float64 express?

  • the set of allowed types (the | means or)
  • a list of methods
  • a tuple
  • a struct

Answer: the set of allowed types (the | means or). It's a type set; | means 'or', listing the permitted underlying types.

What is the widest constraint, equivalent to interface{}?

  • comparable
  • Ordered
  • any
  • Number

Answer: any. any is the widest constraint and is an alias for interface{}.

Which built-in constraint is needed to use == in generic code?

  • any
  • comparable
  • Number
  • Stringer

Answer: comparable. comparable permits == and !=; not all types (slices, maps, funcs) qualify.

Inside a generic method, what does var zero T produce?

  • nil always
  • a panic
  • the first element
  • the zero value of T

Answer: the zero value of T. var zero T is the zero value of whatever type T is instantiated as.

Why does ~int use a tilde?

  • to negate int
  • to also match named types whose underlying type is int
  • to make it a pointer
  • for performance

Answer: to also match named types whose underlying type is int. Without ~ only the exact int matches; ~int also admits type Age int.

Do generics add runtime type-checking overhead like reflection?

  • yes, always
  • only for slices
  • no, they are resolved at compile time
  • only with comparable

Answer: no, they are resolved at compile time. Generics are compile-time, so there's no reflection-style runtime cost.