Skip to content

Intermediate · Packages, testing and generics · topic 25 of 46 · 40 min

Generics: type parameters and constraints

Explanation

Generics (Go 1.18+) let you write one function or type that works for many types, like Max for ints, floats and strings. Type parameters go in square brackets with a constraint saying what the type must support: any, comparable, cmp.Ordered or your own interface. Use generics for containers and algorithms, not for everything.

The problem

Before Go 1.18, if you wanted a function that finds the biggest item in a slice, you wrote MaxInts, MaxFloats and MaxStrings, all identical except the type. Or you used any and type assertions, losing type safety. Generics solve this: write the logic once, and the compiler checks it for every type you use.

A generic function

go
import "cmp"

func Max[T cmp.Ordered](a, b T) T {
	if a > b {
		return a
	}
	return b
}

func main() {
	fmt.Println(Max(3, 7))            // 7
	fmt.Println(Max(2.5, 1.5))        // 2.5
	fmt.Println(Max("chai", "coffee")) // coffee
}
T is a type parameter. cmp.Ordered (Go 1.21+) is a constraint: any type that supports < and >.
  • Type parameter: a placeholder type name in square brackets, like [T any].
  • Constraint: an interface that says what T must support. Here, ordering with < and >.
  • Type inference: Go usually works out T from the arguments, so you write Max(3, 7), not Max[int](3, 7).
  • Instantiation: you can also give the type explicitly: Max[float64](3, 2.5).

Common constraints

ConstraintMeansAllows
anyevery typestoring, passing, returning; no operators
comparabletypes that support == and !=map keys, equality checks
cmp.Orderedintegers, floats and strings< <= > >= and ==
your own interfacea set of types and/or methodswhatever the set allows
go
type Number interface {
	~int | ~int64 | ~float64
}

func Sum[T Number](xs []T) T {
	var total T
	for _, x := range xs {
		total += x
	}
	return total
}

type Paise int64

func main() {
	fmt.Println(Sum([]int{1, 2, 3}))          // 6
	fmt.Println(Sum([]float64{1.5, 2.25}))    // 3.75
	fmt.Println(Sum([]Paise{1000, 2550}))      // 3550 (works because of ~int64)
}
The | lists allowed types. ~int64 means "int64 or any type whose underlying type is int64", like Paise.

Generic types

go
type Stack[T any] struct {
	items []T
}

func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }

func (s *Stack[T]) Pop() (T, bool) {
	var zero T
	if len(s.items) == 0 {
		return zero, false
	}
	v := s.items[len(s.items)-1]
	s.items = s.items[:len(s.items)-1]
	return v, true
}

func main() {
	var undo Stack[string]
	undo.Push("type A")
	undo.Push("type B")
	v, _ := undo.Pop()
	fmt.Println(v) // type B
}
var zero T gives the zero value of whatever T is. Methods use the type's parameter but cannot add new type parameters of their own.

Generics you already get for free

The standard library's slices and maps packages (Go 1.21+) are generic: slices.Sort, slices.Contains, slices.Index, maps.Keys and friends work for any suitable type. The built-in min and max, and the cmp package, use the same ideas. Often you will USE generics far more than you write them.

When not to use generics

  • If the code only ever handles one type, write it for that type.
  • If what differs between types is BEHAVIOUR (each type does it its own way), use an interface with methods, not a type parameter.
  • Do not turn every function generic "to be flexible". It makes code harder to read and errors harder to understand.
  • A good test: you are about to copy-paste a function and only change the types. That is when generics help.
Careful: Constraint interfaces that list types (like Number above) can only be used as constraints, not as ordinary variable types. var n Number is a compile error.
Tip: Start with the generic helpers in slices, maps and cmp. Write your own generic code for containers (Stack, Queue, Cache) and algorithms (Map, Filter, Reduce over slices).

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Generics: type parameters and constraints · 1/7

Generics (Go 1.18+)

Generics let you write one function or type that works for many types, safely.

Prefer a video by a teacher? Find free videos on “golang generics tutorial type parameters constraints cmp.Ordered” on YouTube ↗

In real life: A typed in-memory cache for an API

A food delivery API caches restaurant menus and user profiles for a few minutes to reduce database load. One generic Cache[K, V] type works for both, with full type checking: putting a Profile into the menu cache is a compile error, and no type assertions are needed when reading.

go
package main

import (
	"fmt"
	"sync"
)

type Cache[K comparable, V any] struct {
	mu   sync.Mutex
	data map[K]V
}

func NewCache[K comparable, V any]() *Cache[K, V] {
	return &Cache[K, V]{data: make(map[K]V)}
}

func (c *Cache[K, V]) Set(k K, v V) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.data[k] = v
}

func (c *Cache[K, V]) Get(k K) (V, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	v, ok := c.data[k]
	return v, ok
}

type Menu struct{ Items []string }

func main() {
	menus := NewCache[int, Menu]()
	menus.Set(101, Menu{Items: []string{"Masala dosa", "Filter coffee"}})
	if m, ok := menus.Get(101); ok {
		fmt.Println(m.Items) // [Masala dosa Filter coffee]
	}
}
K must be comparable because it is a map key. The mutex makes it safe for many goroutines (module 6). Real caches also expire old entries.

Practice (0/4 done)

Task 1 · Question

What does this print? func First[T any](xs []T) T { return xs[0] } ... fmt.Println(First([]string{"Pune", "Goa"}))

Task 2 · Question

Why does this not compile? func Biggest[T any](a, b T) T { if a > b { return a } return b }

Task 3 · Question

type ID int64. Which constraint accepts ID?

Task 4 · Practice

Write generic functions Map[T, U any](xs []T, f func(T) U) []U and Filter[T any](xs []T, keep func(T) bool) []T. Use them to turn []int prices in paise into []string like "Rs 4.99", and to keep only prices above Rs 100.

Quick quiz

Question 1

1.In which Go version did generics arrive?

Question 2

2.Which constraint lets you use a type as a map key?

Question 3

3.What does ~int mean in a constraint?

Question 4

4.When is an interface better than a type parameter?

Question 5

5.Can a method declare its own new type parameters?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

In which Go version did generics arrive?

Answer: 1.18

Go 1.18 (March 2022) added type parameters.

Which constraint lets you use a type as a map key?

Answer: comparable

Map keys need ==, which comparable guarantees.

What does ~int mean in a constraint?

Answer: int or any type whose underlying type is int

The tilde includes defined types built on int.

When is an interface better than a type parameter?

Answer: When each type behaves differently and has its own methods

Different behaviour per type is what interfaces are for.

Can a method declare its own new type parameters?

Answer: No, methods can only use the type parameters of their receiver type

Go does not allow type parameters on methods themselves.

Which is a generic type in .NET?Related

Answer: Dictionary<TKey,TValue>

Dictionary has two type parameters.

From Generics: reusable, type-safe code

Which built-in type is generic?Related

Answer: Promise<T>

Promise<T> carries the type of its eventual value.

From Generics

What do generics mainly give you?Related

Answer: Type safety at compile time without casts

Errors move from run time to compile time.

From Generics

function first<T>(list: T[]): T | undefined. What is the type of first([10, 20])?Related

Answer: number | undefined

T is inferred as number from the array, so the return type is number | undefined.

From Generics and utility types

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Packages, testing and generics”. The module exam comes next.
  2. Module 5 exam: Packages, testing and generics · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Concurrency: goroutines, channels and context. 46 topics left in the course before the final exam.

← Previous: Benchmarks and fuzz tests