Collections and Utility Packages

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Go 1.27 (go1.27.2), checked on 2026-10-18. Every runnable program below was vetted with go vet, compiled and run with that toolchain, and the output (including compiler errors, vet reports and panics) is the real output. Go deliberately keeps its core small: the language gives you slices and maps, and the standard library adds the rest as ordinary packages. This page is a tour of those packages that you reach for every day once a program grows past fmt and net/http: the generic helpers in slices, maps and cmp; the older sort package you will still meet in existing code; the three container/... packages; floating-point, arbitrary-precision and bit-level arithmetic; random numbers, both the fast kind and the cryptographically secure kind; the uuid package that arrived in Go 1.27; and the two low-level helpers hash/maphash and unique. The examples use the library catalogue domain (shelf) of this reference.

The page assumes the basics of Arrays and Slices and Maps, and the generics syntax from Generics. Iterator-returning functions such as slices.Values and maps.Keys are explained in Iterators and Range-over-Func. Cryptographic hashing, keys and TLS are on Crypto and Security; this page only covers the random-number side of crypto/rand. If you come from Rust, the equivalents of much of this page are on Collections and Numbers and Numeric Computing; from C, see Numbers and Math.

The packages at a glance

Package What it is for Since

cmp

Ordered constraint, three-way Compare, Less, and Or for defaults

1.21 (Or 1.22)

slices

Generic functions on slices: sort, search, insert, delete, compare, iterator adapters

1.21 (iterators 1.23)

maps

Generic functions on maps: clone, copy, equal, delete, iterator adapters

1.21 (iterators 1.23)

sort

The pre-generics sorting API, based on sort.Interface and reflect-driven sort.Slice

1.0

container/heap

Priority queue algorithms over a user type that implements heap.Interface

1.0

container/list

Doubly linked list

1.0

container/ring

Circular list

1.0

math

Constants, floating-point functions, IEEE 754 helpers

1.0

math/big

Arbitrary-precision Int, Rat and Float

1.0 (Int.Divide 1.27)

math/bits

Bit counting, rotation, reversal and carry arithmetic on fixed-size unsigned integers

1.9

math/rand/v2

Fast pseudo-random numbers: PCG and ChaCha8 sources, generic N

1.22 ((*Rand).N 1.27)

crypto/rand

Cryptographically secure random bytes, integers and text

1.0 (Text 1.24)

uuid

RFC 9562 UUIDs: versions 4 and 7, parsing, comparison and text marshaling

1.27

hash/maphash

Seeded, collision-resistant hashing of bytes and comparable values; the Hasher interface

1.14 (String and Bytes 1.19, Comparable 1.24, Hasher 1.27)

unique

Canonicalizing (interning) comparable values with Handle[T]

1.23

cmp: ordering and defaults

The cmp package is the vocabulary that slices and maps share. It defines:

  • the constraint cmp.Ordered, the set of all types that support the ordering operators <, <=, > and >=: every integer type, every floating-point type and string, together with any defined type whose underlying type is one of those (the ~ forms in the constraint);

  • cmp.Compare(x, y), a three-way comparison that returns a negative number, zero or a positive number (in practice -1, 0 or +1);

  • cmp.Less(x, y), the boolean form; and

  • cmp.Or(vals...), which returns the first argument that is not the zero value of its type (Go 1.22).

Three-way comparison is the signature of every Func function in slices: SortFunc, BinarySearchFunc, MinFunc, CompactFunc and so on take func(a, b E) int. That shape composes, which is what cmp.Or is for: a multi-key ordering is the first comparison that is not zero.

package main

import (
	"cmp"
	"fmt"
	"math"
	"slices"
)

type Config struct {
	Addr    string
	Timeout int
}

func main() {
	fmt.Println(cmp.Compare(1, 2), cmp.Compare("b", "a"), cmp.Compare(3.5, 3.5))
	fmt.Println(cmp.Less("apple", "banana"))

	// NaN sorts before every other float; NaN equals NaN for Compare.
	nan := math.NaN()
	fmt.Println(cmp.Compare(nan, 1), cmp.Compare(nan, nan), cmp.Compare(1, nan))
	fmt.Println(cmp.Less(nan, math.Inf(-1)), nan < math.Inf(-1))
	// -0.0 and +0.0 compare equal.
	fmt.Println(cmp.Compare(math.Copysign(0, -1), 0))

	floats := []float64{3, nan, 1, math.Inf(1), -2, math.Inf(-1)}
	slices.Sort(floats)
	fmt.Println(floats)

	// cmp.Or returns the first argument that is not the zero value.
	def := Config{Addr: ":8080", Timeout: 30}
	user := Config{Timeout: 5}
	merged := Config{
		Addr:    cmp.Or(user.Addr, def.Addr),
		Timeout: cmp.Or(user.Timeout, def.Timeout),
	}
	fmt.Printf("%+v\n", merged)
	fmt.Println(cmp.Or(0, 0, 0) == 0, cmp.Or[string]() == "")

	// It works for any comparable type, e.g. pointers and structs.
	fmt.Println(cmp.Or(Config{}, def) == def)
}
-1 1 0
true
-1 0 1
true false
0
[NaN -Inf -2 1 3 +Inf]
{Addr::8080 Timeout:5}
true true
true

Source: cmp package

Points to remember:

  • Floating-point rules. cmp.Compare and slices.Sort define a total order for floats that the < operator does not: a NaN is less than any non-NaN and equal to another NaN, and -0.0 equals +0.0. That is why the Sort call above puts NaN first and does not corrupt the result. With a hand-written a < b comparison, any NaN makes the ordering inconsistent.

  • cmp.Or evaluates every argument. It is a function, not an operator, so cmp.Or(expensive(), fallback()) calls both. It also treats a legitimate zero as "unset": a Timeout of 0 meaning "no timeout" cannot be distinguished from a missing value. For such fields use a pointer or a separate bool.

  • Or needs comparable. It compares each argument with the zero value using ==, so slices, maps and functions are rejected at compile time.

  • Do not subtract. The older idiom return a.Year - b.Year works for small numbers but overflows for large or extreme ones:

package main

import (
	"cmp"
	"fmt"
	"math"
)

func main() {
	a, b := math.MaxInt-1, -5
	fmt.Println(a-b > 0)           // wraps: wrongly reports a < b
	fmt.Println(cmp.Compare(a, b)) // correct: 1
}
false
1

cmp.Compare is the right replacement for the subtraction. Passing a type that is not ordered, or a non-comparable one to cmp.Or, fails at compile time with the type set in the message. (In the error demos on this page the fragments omit the package clause and imports, so the line numbers in the messages refer to the complete file.)

type Book struct {
	Title string
	Year  int
}

books := []Book{{"Emma", 1815}, {"Dune", 1965}}
slices.Sort(books)                       // Book is not an ordered type
fmt.Println(cmp.Compare(books[0], books[1]))
fmt.Println(cmp.Or([]int{}, []int{1}))   // slices are not comparable
./main.go:16:13: Book does not satisfy cmp.Ordered (Book missing in ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string)
./main.go:17:25: Book does not satisfy cmp.Ordered (Book missing in ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string)
./main.go:18:20: []int does not satisfy comparable

Source: cmp.Compare and cmp.Or

Likewise a "less" function is the wrong shape for the Func variants, which want an int:

slices.SortFunc(s, func(a, b int) bool { return a < b })
./main.go:11:21: in call to slices.SortFunc, type func(a int, b int) bool of func(a, b int) bool {…} does not match inferred type func(a int, b int) int for func(a E, b E) int

Source: cmp.Ordered

slices and maps: a reference

The generic functions in slices and maps replaced most hand-written loops. The behavior of slices, append and capacity is on Arrays and Slices and the map semantics on Maps; this section is the complete reference table, followed by an end-to-end example on the shelf domain and the pitfalls.

slices

Function What it does Since

slices.Sort(s)

Sorts an ordered slice in place; NaNs first

1.21

slices.SortFunc(s, cmp)

Sorts with a three-way cmp; not stable

1.21

slices.SortStableFunc(s, cmp)

Like SortFunc but keeps equal elements in input order

1.21

slices.IsSorted(s), slices.IsSortedFunc(s, cmp)

Reports whether the slice is already sorted

1.21

slices.BinarySearch(s, target)

Index of target in a sorted slice, and whether it was found

1.21

slices.BinarySearchFunc(s, target, cmp)

The same with a custom comparison; the target may have another type than the elements

1.21

slices.Min(s), slices.Max(s)

Smallest and largest element; panics on an empty slice

1.21

slices.MinFunc(s, cmp), slices.MaxFunc(s, cmp)

The same with a custom comparison; the first of equal elements wins

1.21

slices.Equal(a, b), slices.EqualFunc(a, b, eq)

Same length and equal elements

1.21

slices.Compare(a, b), slices.CompareFunc(a, b, cmp)

Lexicographic three-way comparison

1.21

slices.Index(s, v), slices.IndexFunc(s, f)

First matching position or -1

1.21

slices.Contains(s, v), slices.ContainsFunc(s, f)

Membership test

1.21

slices.Insert(s, i, v...)

Inserts values before index i; panics if i > len(s)

1.21

slices.Delete(s, i, j)

Removes s[i:j]; zeroes the vacated tail

1.21 (zeroing 1.22)

slices.DeleteFunc(s, f)

Removes every element for which f is true

1.21

slices.Replace(s, i, j, v...)

Replaces s[i:j] with the values

1.21

slices.Compact(s), slices.CompactFunc(s, eq)

Removes consecutive duplicates

1.21

slices.Clone(s)

Shallow copy with a new backing array; Clone(nil) stays nil

1.21

slices.Clip(s)

Reduces capacity to length so the next append reallocates

1.21

slices.Grow(s, n)

Guarantees room for n more appends

1.21

slices.Reverse(s)

Reverses in place

1.21

slices.Concat(ss...)

New slice with all the elements of the arguments

1.22

slices.Repeat(s, n)

New slice repeating s n times

1.23

slices.All(s), slices.Values(s), slices.Backward(s)

Iterators over index-value pairs, values or in reverse

1.23

slices.Collect(seq), slices.AppendSeq(s, seq)

Drain an iterator into a new or existing slice

1.23

slices.Sorted(seq), slices.SortedFunc(seq, cmp), slices.SortedStableFunc(seq, cmp)

Collect an iterator and sort the result

1.23

slices.Chunk(s, n)

Iterator over consecutive sub-slices of length n (the last may be shorter); n < 1 panics

1.23

maps

Function What it does Since

maps.Clone(m)

Shallow copy; Clone(nil) stays nil

1.21

maps.Copy(dst, src)

Copies all entries of src into dst, overwriting existing keys

1.21

maps.Equal(a, b), maps.EqualFunc(a, b, eq)

Same keys and equal values

1.21

maps.DeleteFunc(m, f)

Deletes every entry for which f(k, v) is true

1.21

maps.All(m), maps.Keys(m), maps.Values(m)

Iterators in the map’s random order

1.23

maps.Insert(m, seq)

Adds the pairs of an iterator

1.23

maps.Collect(seq)

New map from an iterator of pairs

1.23

Source: slices package

Source: maps package

The next program combines them on the shelf domain: a multi-key sort with cmp.Or, a binary search by ISBN, grouping, ordered iteration over a map with slices.Sorted(maps.Keys(m)), MinFunc and Chunk for paging.

package main

import (
	"cmp"
	"fmt"
	"maps"
	"slices"
	"strings"
)

// Book is a trimmed-down catalog.Book for the example.
type Book struct {
	ISBN   string
	Title  string
	Author string
	Year   int
}

func main() {
	books := []Book{
		{"9780441013593", "Dune", "Herbert", 1965},
		{"9780141439518", "Emma", "Austen", 1815},
		{"9780141439884", "Persuasion", "Austen", 1817},
		{"9780199535569", "Ulysses", "Joyce", 1922},
		{"9780141439471", "Dubliners", "Joyce", 1914},
	}

	// Multi-key ordering: author, then year descending, then title.
	slices.SortFunc(books, func(a, b Book) int {
		return cmp.Or(
			strings.Compare(a.Author, b.Author),
			cmp.Compare(b.Year, a.Year), // reversed operands = descending
			cmp.Compare(a.Title, b.Title),
		)
	})
	for _, b := range books {
		fmt.Printf("%-8s %d %s\n", b.Author, b.Year, b.Title)
	}

	// Binary search needs the same ordering on the searched key only.
	byISBN := slices.SortedFunc(slices.Values(books), func(a, b Book) int {
		return cmp.Compare(a.ISBN, b.ISBN)
	})
	i, found := slices.BinarySearchFunc(byISBN, "9780199535569", func(b Book, isbn string) int {
		return cmp.Compare(b.ISBN, isbn)
	})
	fmt.Println(i, found, byISBN[i].Title)

	// Group titles by author, then walk the authors in order.
	titles := map[string][]string{}
	for _, b := range books {
		titles[b.Author] = append(titles[b.Author], b.Title)
	}
	for _, author := range slices.Sorted(maps.Keys(titles)) {
		fmt.Println(author, titles[author])
	}

	oldest := slices.MinFunc(books, func(a, b Book) int { return cmp.Compare(a.Year, b.Year) })
	fmt.Println("oldest:", oldest.Title)

	for page := range slices.Chunk(slices.Sorted(maps.Keys(titles)), 2) {
		fmt.Println("page:", page)
	}
}
Austen   1817 Persuasion
Austen   1815 Emma
Herbert  1965 Dune
Joyce    1922 Ulysses
Joyce    1914 Dubliners
3 true Ulysses
Austen [Persuasion Emma]
Herbert [Dune]
Joyce [Ulysses Dubliners]
oldest: Emma
page: [Austen Herbert]
page: [Joyce]

Notes on the program:

  • In SortFunc the order of the operands picks the direction: cmp.Compare(b.Year, a.Year) sorts newest first.

  • BinarySearchFunc only requires that the slice is sorted by the searched key; the program re-sorts a copy by ISBN first. The comparison function receives an element and the target, in that order.

  • Maps iterate in a random order, so the program sorts the keys before printing or chunking them. Without the sort, the pages would differ from run to run.

  • Sort and SortFunc are not stable. Use SortStableFunc when records that compare equal must keep their input order (for example when sorting a table by one column after sorting by another).

Pitfalls

  • The result must be used. Insert, Delete, Compact, Replace, Grow, Clip and friends return the new slice, because the length (and maybe the backing array) changes. go vet reports a call whose result is dropped:

s := []int{1, 2, 3}
slices.Delete(s, 0, 1)
slices.Sort(s)
slices.Insert(s, 0, 9)
./main.go:10:2: result of slices.Delete call not used
./main.go:12:2: result of slices.Insert call not used

(Sort returns nothing, so it is not reported.)

  • Sorted input is a precondition, not a check. BinarySearch on an unsorted slice neither panics nor errors; it returns an arbitrary answer. Compact removes only neighbours, so it needs a sorted slice to remove every duplicate.

  • Aliasing. Delete and Insert modify the backing array of the argument; the old slice header now shows a shifted array. Clone first when the original must survive, and Clip to force a copy on the next append.

  • Out-of-range arguments panic with the runtime’s messages, as the program below shows.

package main

import (
	"fmt"
	"slices"
)

func try(name string, f func()) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("%s: panic: %v\n", name, r)
		}
	}()
	f()
}

func main() {
	// Delete shifts the tail down and (since Go 1.22) zeroes the vacated slots,
	// so pointers there no longer keep objects alive.
	s := []*int{new(int), new(int), new(int), new(int)}
	t := slices.Delete(s, 1, 3)
	fmt.Println(len(t), len(s), s[2] == nil, s[3] == nil)

	// Compact only removes *consecutive* duplicates.
	fmt.Println(slices.Compact([]int{1, 1, 2, 1, 1, 3, 3}))

	// BinarySearch on an unsorted slice is not an error: it just returns nonsense.
	i, found := slices.BinarySearch([]int{9, 1, 8, 2, 7}, 2)
	fmt.Println(i, found)

	// Clip and the append aliasing trap.
	base := make([]int, 3, 10)
	grown := append(base, 99)
	clipped := append(slices.Clip(base), 100) // capacity 3: forces a new array
	fmt.Println(grown[3], clipped[3], len(base), cap(slices.Clip(base)))

	try("Insert", func() { _ = slices.Insert([]int{1, 2}, 5, 9) })
	try("Min", func() { _ = slices.Min([]int{}) })
	try("Chunk", func() {
		for range slices.Chunk([]int{1}, 0) {
		}
	})
	try("Delete", func() { _ = slices.Delete([]int{1, 2, 3}, 2, 1) })
	try("Repeat", func() { _ = slices.Repeat([]int{1}, -1) })
}
2 4 true true
[1 2 1 3]
2 false
99 100 3 3
Insert: panic: runtime error: slice bounds out of range [5:2]
Min: panic: slices.Min: empty list
Chunk: panic: cannot be less than 1
Delete: panic: runtime error: slice bounds out of range [2:1:]
Repeat: panic: cannot be negative

Since Go 1.22, Delete, DeleteFunc, Compact, CompactFunc and Replace zero the elements between the new and old length, which lets the garbage collector free objects that a slice of pointers would otherwise keep alive.

sort: the legacy package

The sort package predates generics. It has three layers:

  • the interface sort.Interface (Len, Less(i, j int) bool, Swap(i, j int)) with sort.Sort, sort.Stable, sort.IsSorted and the sort.Reverse adapter;

  • the convenience function sort.Slice (and SliceStable, SliceIsSorted), which takes any slice as any plus a less function over indices and swaps elements with reflect.Swapper; and

  • typed helpers: sort.Ints, sort.Strings, sort.Float64s, the IntSlice, StringSlice and Float64Slice types, and the searches sort.Search, sort.SearchInts, sort.SearchStrings, sort.SearchFloat64s and sort.Find (Go 1.19).

package main

import (
	"fmt"
	"slices"
	"sort"
	"strings"
)

type Book struct {
	Title string
	Year  int
}

// byYear implements sort.Interface, the pre-generics way.
type byYear []Book

func (s byYear) Len() int           { return len(s) }
func (s byYear) Less(i, j int) bool { return s[i].Year < s[j].Year }
func (s byYear) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

func main() {
	books := []Book{{"Ulysses", 1922}, {"Emma", 1815}, {"Dune", 1965}, {"Dubliners", 1914}}

	// 1. sort.Slice: reflection-based swapper, `less` gets indices.
	sort.Slice(books, func(i, j int) bool { return books[i].Year < books[j].Year })
	fmt.Println(books)

	// 2. sort.Sort with a named type implementing sort.Interface.
	sort.Sort(sort.Reverse(byYear(books)))
	fmt.Println(books)

	// 3. The modern replacement: generic, no reflection, three-way compare.
	slices.SortFunc(books, func(a, b Book) int { return a.Year - b.Year })
	fmt.Println(books)

	// Typed helpers for the basic types.
	ints := []int{5, 2, 8}
	sort.Ints(ints)
	words := strings.Fields("pear apple fig")
	sort.Strings(words)
	fmt.Println(ints, words, sort.IntsAreSorted(ints))

	// sort.Search: smallest index in [0,n) where f is true (f must be monotone).
	years := []int{1815, 1914, 1922, 1965}
	i := sort.Search(len(years), func(i int) bool { return years[i] >= 1920 })
	fmt.Println(i, years[i])
	// Not found: Search returns n, so check the bounds yourself.
	j := sort.Search(len(years), func(i int) bool { return years[i] >= 2000 })
	fmt.Println(j, j == len(years))

	// sort.SearchInts and slices.BinarySearch also report whether it was found.
	k := sort.SearchInts(years, 1914)
	fmt.Println(k, k < len(years) && years[k] == 1914)
	pos, ok := slices.BinarySearch(years, 1914)
	fmt.Println(pos, ok)

	// sort.Find (Go 1.19) is the three-way variant and reports found.
	pos, ok = sort.Find(len(years), func(i int) int { return 1922 - years[i] })
	fmt.Println(pos, ok)

	// Stability: sort.SliceStable keeps equal elements in input order.
	people := []struct {
		Name string
		Age  int
	}{{"Ann", 30}, {"Bob", 25}, {"Cat", 30}, {"Dan", 25}}
	sort.SliceStable(people, func(i, j int) bool { return people[i].Age < people[j].Age })
	fmt.Println(people)

	defer func() { fmt.Println("recovered:", recover()) }()
	sort.Slice("not a slice", func(i, j int) bool { return false })
}
[{Emma 1815} {Dubliners 1914} {Ulysses 1922} {Dune 1965}]
[{Dune 1965} {Ulysses 1922} {Dubliners 1914} {Emma 1815}]
[{Emma 1815} {Dubliners 1914} {Ulysses 1922} {Dune 1965}]
[2 5 8] [apple fig pear] true
2 1922
4 true
1 true
1 true
2 true
[{Bob 25} {Dan 25} {Ann 30} {Cat 30}]
recovered: reflect: call of Swapper on string Value

Source: sort package

The program shows the three ways to sort, then the search functions. sort.Search(n, f) returns the smallest index i in [0, n) for which f(i) is true, assuming f is false and then true across the range; it returns n when nothing matches, so the caller checks the bound and the equality. sort.Find reports found like slices.BinarySearch.

Which one should new code use?

Legacy Modern replacement Why

sort.Ints(s), sort.Strings(s), sort.Float64s(s)

slices.Sort(s)

Since Go 1.22 the sort versions just call slices.Sort

sort.Slice(s, func(i, j int) bool {...})

slices.SortFunc(s, func(a, b T) int {...})

Generic, no reflection, type-checked, elements passed by value

sort.SliceStable

slices.SortStableFunc

Same, stable

sort.Sort(sort.Reverse(x))

slices.SortFunc with swapped operands, or slices.Sort then slices.Reverse

No named type with three methods

sort.Search, sort.SearchInts

slices.BinarySearch, slices.BinarySearchFunc

Return (index, found)

sort.Sort(byYear(x))

keep, if the type already implements sort.Interface

A data structure that is not a slice (for example a type that wraps a database cursor) still needs the interface

The sort.Slice family has two traps that the generic versions remove: passing something that is not a slice compiles but panics at run time (the last line of the program shows the panic message), and the less function indexes the original slice variable, so it silently sorts wrongly if the closure refers to a different slice than the one being sorted. Both sort.Slice and slices.SortFunc require a consistent ordering: less(a, b) and less(b, a) must not both be true, and the relation must be transitive. A comparison like <= breaks that rule.

container/heap

container/heap implements a binary min-heap on any type that satisfies heap.Interface:

type Interface interface {
	sort.Interface   // Len, Less, Swap
	Push(x any)      // add x as element Len()
	Pop() any        // remove and return element Len()-1
}

You implement the five methods on a slice type, and the package supplies heap.Init, heap.Push, heap.Pop, heap.Fix and heap.Remove. The Push and Pop methods you write only append to and truncate the slice; you never call them directly. The heap.Push and heap.Pop functions call them and then restore the heap order, in O(log n). The smallest element, according to Less, is at index 0.

The next program is a queue of loans ordered by due date. Each element stores its own index so a renewal can call heap.Fix and a cancellation can call heap.Remove in O(log n).

package main

import (
	"container/heap"
	"fmt"
	"time"
)

// Loan is a due-date entry; index is maintained by the heap for Fix/Remove.
type Loan struct {
	Member string
	Book   string
	Due    time.Time
	index  int
}

// dueQueue is a min-heap of loans ordered by due date.
type dueQueue []*Loan

func (q dueQueue) Len() int           { return len(q) }
func (q dueQueue) Less(i, j int) bool { return q[i].Due.Before(q[j].Due) }
func (q dueQueue) Swap(i, j int) {
	q[i], q[j] = q[j], q[i]
	q[i].index = i
	q[j].index = j
}

// Push and Pop are called by package heap; do not call them directly.
func (q *dueQueue) Push(x any) {
	l := x.(*Loan)
	l.index = len(*q)
	*q = append(*q, l)
}

func (q *dueQueue) Pop() any {
	old := *q
	n := len(old)
	l := old[n-1]
	old[n-1] = nil // let the GC reclaim the loan
	l.index = -1
	*q = old[:n-1]
	return l
}

func main() {
	day := func(d int) time.Time { return time.Date(2026, time.November, d, 0, 0, 0, 0, time.UTC) }
	q := &dueQueue{}
	heap.Init(q)

	loans := map[string]*Loan{
		"dune":      {Member: "ann", Book: "Dune", Due: day(20)},
		"emma":      {Member: "bob", Book: "Emma", Due: day(5)},
		"ulysses":   {Member: "cat", Book: "Ulysses", Due: day(12)},
		"dubliners": {Member: "dan", Book: "Dubliners", Due: day(30)},
	}
	for _, k := range []string{"dune", "emma", "ulysses", "dubliners"} {
		heap.Push(q, loans[k])
	}

	fmt.Println("next due:", (*q)[0].Book, (*q)[0].Due.Format("Jan 2"))

	// Renewal: change the key, then restore the heap invariant with Fix.
	loans["emma"].Due = day(25)
	heap.Fix(q, loans["emma"].index)

	// Remove an arbitrary element by its index.
	heap.Remove(q, loans["ulysses"].index)

	for q.Len() > 0 {
		l := heap.Pop(q).(*Loan)
		fmt.Println(l.Due.Format("Jan 2"), l.Book, l.Member)
	}
}
next due: Emma Nov 5
Nov 20 Dune ann
Nov 25 Emma bob
Nov 30 Dubliners dan
Function Cost Use

heap.Init(h)

O(n)

Establish the heap order in a slice that was filled directly

heap.Push(h, x)

O(log n)

Insert

heap.Pop(h)

O(log n)

Remove and return the minimum

heap.Fix(h, i)

O(log n)

Re-establish the order after the element at i changed

heap.Remove(h, i)

O(log n)

Remove the element at i

Common mistakes:

  • Pointer receivers. Push and Pop change the slice length, so they need a pointer receiver, while Len, Less and Swap can use a value receiver. A missing method is a compile error that names the method:

type q []int

func (q q) Len() int           { return len(q) }
func (q q) Less(i, j int) bool { return q[i] < q[j] }
func (q q) Swap(i, j int)      { q[i], q[j] = q[j], q[i] }
func (q *q) Push(x any)        { *q = append(*q, x.(int)) }

func main() {
	h := &q{}
	heap.Push(h, 1)
}
./main.go:14:12: cannot use h (variable of type *q) as heap.Interface value in argument to heap.Push: *q does not implement heap.Interface (missing method Pop)
  • Mutating the key without Fix. Changing Due on an element that is in the heap silently breaks the invariant.

  • Calling the methods instead of the functions. h.Push(x) appends without sifting; always use heap.Push(h, x).

  • Not clearing the popped slot. Pop above sets old[n-1] = nil so the garbage collector can free the loan.

  • any everywhere. The interface predates generics, so Pop() returns any and needs a type assertion. A small generic wrapper hides that and takes the ordering as a function:

package main

import (
	"container/heap"
	"fmt"
)

// PQ is a small generic wrapper that hides the any-typed heap API.
type PQ[T any] struct{ h inner[T] }

type inner[T any] struct {
	items []T
	less  func(a, b T) bool
}

func (h inner[T]) Len() int           { return len(h.items) }
func (h inner[T]) Less(i, j int) bool { return h.less(h.items[i], h.items[j]) }
func (h inner[T]) Swap(i, j int)      { h.items[i], h.items[j] = h.items[j], h.items[i] }
func (h *inner[T]) Push(x any)        { h.items = append(h.items, x.(T)) }
func (h *inner[T]) Pop() any {
	n := len(h.items)
	x := h.items[n-1]
	var zero T
	h.items[n-1] = zero
	h.items = h.items[:n-1]
	return x
}

func NewPQ[T any](less func(a, b T) bool) *PQ[T] {
	return &PQ[T]{h: inner[T]{less: less}}
}

func (p *PQ[T]) Push(x T) { heap.Push(&p.h, x) }
func (p *PQ[T]) Pop() T   { return heap.Pop(&p.h).(T) }
func (p *PQ[T]) Len() int { return p.h.Len() }

func main() {
	pq := NewPQ(func(a, b string) bool { return len(a) < len(b) })
	for _, s := range []string{"Persuasion", "Emma", "Dubliners", "Ulysses"} {
		pq.Push(s)
	}
	for pq.Len() > 0 {
		fmt.Print(pq.Pop(), " ")
	}
	fmt.Println()
}
Emma Ulysses Dubliners Persuasion

The standard library has no generic heap; the wrapper above, a priority queue from a third-party module, or sorting a slice once are the usual options. A heap is also not a sorted slice: only the minimum is guaranteed to be at index 0, and iterating the underlying slice shows the internal order. It is not stable either (equal priorities pop in an unspecified order, which is why the program uses titles of different lengths).

container/list and container/ring

container/list is a doubly linked list. Its zero value is an empty list ready to use, PushFront/PushBack/InsertBefore/InsertAfter return the new *list.Element, and Remove, MoveToFront, MoveToBack, MoveBefore and MoveAfter take an element and run in O(1). An Element exposes Value any, Next() and Prev(). Because the values are any, you assert them back on reading.

The classic use is an LRU cache: a map from key to element for lookup, and a list ordered by recency, where a hit moves the element to the front and an insertion beyond the capacity evicts the back.

container/ring is a circular list: there is no head or tail, every *ring.Ring is an element and a pointer to any of them refers to the whole ring. ring.New(n) builds a ring of n elements, Next, Prev and Move(n) walk it, Do visits every element, Len counts them (in O(n)), and Link/Unlink splice rings together or cut elements out. A ring with a fixed size is a natural bounded history, for example the last three searches:

package main

import (
	"container/list"
	"container/ring"
	"fmt"
)

// lru is a least-recently-used cache of book titles by ISBN.
type lru struct {
	cap   int
	order *list.List // front = most recently used
	items map[string]*list.Element
}

type entry struct{ isbn, title string }

func newLRU(n int) *lru {
	return &lru{cap: n, order: list.New(), items: map[string]*list.Element{}}
}

func (c *lru) Get(isbn string) (string, bool) {
	el, ok := c.items[isbn]
	if !ok {
		return "", false
	}
	c.order.MoveToFront(el)
	return el.Value.(entry).title, true
}

func (c *lru) Put(isbn, title string) {
	if el, ok := c.items[isbn]; ok {
		el.Value = entry{isbn, title}
		c.order.MoveToFront(el)
		return
	}
	c.items[isbn] = c.order.PushFront(entry{isbn, title})
	if c.order.Len() > c.cap {
		last := c.order.Back()
		delete(c.items, last.Value.(entry).isbn)
		c.order.Remove(last)
	}
}

func (c *lru) Keys() []string {
	var keys []string
	for el := c.order.Front(); el != nil; el = el.Next() {
		keys = append(keys, el.Value.(entry).isbn)
	}
	return keys
}

func main() {
	c := newLRU(2)
	c.Put("a", "Dune")
	c.Put("b", "Emma")
	c.Get("a")            // a becomes most recent
	c.Put("c", "Ulysses") // evicts b
	_, okB := c.Get("b")
	fmt.Println(c.Keys(), okB)

	// container/ring: a fixed-size circular buffer of recent searches.
	r := ring.New(3)
	for _, q := range []string{"dune", "emma", "ulysses", "dubliners"} {
		r.Value = q
		r = r.Next()
	}
	r.Do(func(v any) { fmt.Print(v, " ") }) // oldest to newest
	fmt.Println(r.Len())

	// Rotate moves the starting point; Link and Unlink splice rings.
	r = r.Move(1)
	r.Do(func(v any) { fmt.Print(v, " ") })
	fmt.Println()
}
[c a] false
emma ulysses dubliners 3
ulysses dubliners emma

When should you use them? Rarely. Linked lists have poor cache locality and cost one allocation per element, so a slice is faster for almost every workload; list earns its place when you need O(1) removal or reordering of an element you already hold, as in the LRU cache. ring suits a fixed-size buffer where overwriting the oldest entry is the policy, although a slice with an index modulo its length does the same with fewer allocations. Neither has a generic version in the standard library.

math

The math package holds the floating-point functions of the IEEE 754 world and the limits of the integer types. Everything operates on float64 unless the name says otherwise; there is no generic version, so float32 code converts. Constants are untyped until used, so math.Pi and the exact constant expression 0.1 + 0.2 have arbitrary precision at compile time and only become 64-bit values when assigned to a variable (see Variables, Constants and iota).

Group Members

Integer limits

MaxInt8, MinInt8, MaxUint8, …​, MaxInt64, MinInt64, MaxUint64, and MaxInt, MinInt, MaxUint (the size of int)

Float limits

MaxFloat32, SmallestNonzeroFloat32, MaxFloat64, SmallestNonzeroFloat64

Constants

Pi, E, Phi, Sqrt2, SqrtE, Ln2, Log2E, …​ (arbitrary precision as constants)

Rounding

Floor, Ceil, Trunc, Round (half away from zero), RoundToEven

Remainders

Mod (sign of the dividend, like %), Remainder (IEEE 754, nearest integer quotient), Modf, Frexp, Ldexp

Powers and roots

Pow, Pow10, Sqrt, Cbrt, Hypot, Exp, Exp2, Expm1

Logarithms

Log, Log2, Log10, Log1p, Logb, Ilogb

Trigonometry

Sin, Cos, Tan, Asin, Acos, Atan, Atan2, Sincos, Sinh, Cosh, Tanh, Asinh, …​

Special values

Inf(sign), NaN(), IsInf(x, sign), IsNaN(x), Signbit, Copysign, Nextafter

Comparison

Max, Min, Dim, Abs (NaN-propagating Max/Min)

Representation

Float64bits, Float64frombits, Float32bits, Float32frombits

Special functions

Gamma, Lgamma, Erf, Erfc, Erfinv, J0, Y0, …​

Fused multiply-add

FMA(x, y, z)

package main

import (
	"fmt"
	"math"
)

func main() {
	// Constants are exact (untyped) until they are used.
	fmt.Println(math.MaxInt8, math.MinInt64, uint64(math.MaxUint64))
	fmt.Println(math.MaxInt, math.Pi)
	fmt.Println(math.MaxFloat64, math.SmallestNonzeroFloat64)

	// Rounding family.
	for _, x := range []float64{2.5, -2.5, 2.4, 3.5} {
		fmt.Println(x, math.Floor(x), math.Ceil(x), math.Trunc(x), math.Round(x), math.RoundToEven(x))
	}

	// Special values.
	inf, nan := math.Inf(1), math.NaN()
	fmt.Println(inf, -inf, nan, 1/inf, math.IsInf(inf, 0), math.IsNaN(nan))
	fmt.Println(nan == nan, nan != nan)
	var zero float64
	fmt.Println(1/zero, -1/zero, zero/zero) // no panic for floats
	fmt.Println(math.Sqrt(-1), math.Log(0), math.Log(-1))

	// Mod keeps the sign of the dividend; Remainder is IEEE 754 round-to-nearest.
	fmt.Println(math.Mod(-7, 3), math.Remainder(7, 3), -7%3)
	fmt.Println(math.Hypot(3, 4), math.Cbrt(27), math.Pow(2, 10), math.Pow(-8, 1.0/3))
	fmt.Println(math.Abs(-0.5), math.Max(1, nan), math.Min(math.Copysign(0, -1), 0), math.Copysign(3, -1))

	// Floating-point equality: compare with a tolerance.
	x, y := 0.1, 0.2 // variables: the constant expression 0.1+0.2 would be exact
	a, b := x+y, 0.3
	fmt.Println(a == b, math.Abs(a-b) < 1e-9, a)

	// Integer overflow wraps silently; detect it before it happens.
	var i int8 = math.MaxInt8
	i++
	fmt.Println(i)
	maxv := int64(math.MaxInt64)
	fmt.Println(maxv+1 < 0, addOverflows(maxv, 1))

	// Frexp/Modf/Float64bits expose the representation.
	frac, exp := math.Frexp(48)
	ip, fp := math.Modf(3.75)
	fmt.Println(frac, exp, ip, fp)
	fmt.Printf("%#016x %v\n", math.Float64bits(1.0), math.Float64frombits(0x4000000000000000))
	fmt.Println(math.Nextafter(1, 2) - 1)
}

// addOverflows reports whether a+b would overflow int64.
func addOverflows(a, b int64) bool {
	return (b > 0 && a > math.MaxInt64-b) || (b < 0 && a < math.MinInt64-b)
}
127 -9223372036854775808 18446744073709551615
9223372036854775807 3.141592653589793
1.7976931348623157e+308 5e-324
2.5 2 3 2 3 2
-2.5 -3 -2 -2 -3 -2
2.4 2 3 2 2 2
3.5 3 4 3 4 4
+Inf -Inf NaN 0 true true
false true
+Inf -Inf NaN
NaN -Inf NaN
-1 1 -1
5 3 1024 NaN
0.5 NaN -0 -3
false true 0.30000000000000004
-128
true true
0.75 6 3 0.75
0x3ff0000000000000 2
2.220446049250313e-16

Source: math package

What the output teaches:

  • Float division by zero does not panic. 1/zero is +Inf, zero/zero is NaN. Integer division by zero does panic.

  • NaN is never equal to anything, including itself, so x == x is false for a NaN. Use math.IsNaN. Using a NaN as a map key creates entries that can never be found again; see Maps.

  • math.Round rounds halves away from zero (2.5 becomes 3), while RoundToEven rounds them to the even neighbour (2.5 becomes 2), which avoids a systematic bias. fmt uses round-half-even when formatting with %.0f.

  • % versus math.Mod: both keep the sign of the dividend (-7 % 3 is -1), unlike Python; math.Remainder follows IEEE 754 and returns 1 for Remainder(7, 3) because 7/3 rounds to 2.

  • Equality of floats: 0.1 + 0.2 computed from variables is not 0.3. Compare with a tolerance (absolute, relative, or both) suited to the problem. The constant expression 0.1 + 0.2 is exactly 0.3 because constant arithmetic is exact; the example uses variables to show the run-time behavior.

  • Integer overflow wraps silently for fixed-size integers (int8 127 plus 1 is -128). The language has no overflow trap and no checked arithmetic; detect it before it happens, as addOverflows does, or with math/bits for unsigned values (below).

  • Signed zero: -0 exists and math.Min(-0, 0) returns -0; cmp.Compare and == treat both zeros as equal.

math/big

math/big provides three arbitrary-precision types:

Type Represents Constructors

big.Int

Signed integers of any size

big.NewInt(int64), new(big.Int).SetString(s, base), SetBytes, SetUint64, SetBit

big.Rat

Exact rationals a/b

big.NewRat(a, b), SetString("3/4"), SetFrac, SetFloat64

big.Float

Floating-point numbers with a chosen precision in bits (SetPrec) and RoundingMode

big.NewFloat(f), SetInt, SetString, Parse

The API is designed around in-place results: z.Add(x, y) sets z = x + y and returns z, so expressions chain and no allocation is needed when z already has capacity. The receiver may alias an operand (z.Mul(z, z)). There are no operators; you cannot write a + b for a *big.Int. The zero value of each type is a ready-to-use 0, but never copy a big.Int or big.Float by assigning the struct: the copy shares the underlying slice, so later writes to one corrupt the other. Copy with new(big.Int).Set(x).

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// Int: arbitrary precision, methods write into the receiver.
	f := big.NewInt(1)
	for i := int64(2); i <= 30; i++ {
		f.Mul(f, big.NewInt(i)) // f = f * i
	}
	fmt.Println("30! =", f, f.BitLen(), "bits")

	n, ok := new(big.Int).SetString("123456789012345678901234567890", 10)
	fmt.Println(n, ok)
	_, ok = new(big.Int).SetString("12x", 10)
	fmt.Println(ok)

	a, b := big.NewInt(-7), big.NewInt(2)
	q, r := new(big.Int).QuoRem(a, b, new(big.Int)) // truncated, like Go's / and %
	d, m := new(big.Int).DivMod(a, b, new(big.Int)) // Euclidean: remainder >= 0
	fmt.Println(q, r, d, m)

	// Go 1.27: Divide with an explicit rounding mode.
	for _, mode := range []big.RoundingMode{big.Trunc, big.Floor, big.Round, big.Ceil} {
		q, r := new(big.Int).Divide(a, b, new(big.Int), mode)
		fmt.Println(mode, q, r)
	}

	fmt.Println(new(big.Int).Exp(big.NewInt(2), big.NewInt(100), nil))
	fmt.Println(new(big.Int).Exp(big.NewInt(3), big.NewInt(1000), big.NewInt(1_000_007))) // modular
	fmt.Println(new(big.Int).GCD(nil, nil, big.NewInt(84), big.NewInt(36)))
	fmt.Println(new(big.Int).ModInverse(big.NewInt(3), big.NewInt(11)))
	fmt.Println(big.NewInt(1_000_003).ProbablyPrime(20), a.Sign(), a.Cmp(b), a.CmpAbs(b))
	fmt.Printf("%x %s %v\n", f, f.Text(36), f.IsInt64())

	// Rat: exact fractions.
	x := big.NewRat(1, 3)
	y := big.NewRat(1, 6)
	s := new(big.Rat).Add(x, y)
	fmt.Println(s, s.FloatString(5), s.Num(), s.Denom())

	// Float: arbitrary precision (in bits), explicit rounding mode.
	p := new(big.Float).SetPrec(200).SetInt64(2)
	root := new(big.Float).SetPrec(200).Sqrt(p)
	fmt.Println(root.Text('f', 50))
	fmt.Println(new(big.Float).SetPrec(53).Quo(big.NewFloat(1), big.NewFloat(3)))

	// The zero value is a valid 0, and a result may alias an operand.
	var z big.Int
	fmt.Println(z.String())
	z.Add(&z, big.NewInt(5))
	z.Mul(&z, &z)
	fmt.Println(&z)
}
30! = 265252859812191058636308480000000 108 bits
123456789012345678901234567890 true
false
-3 -1 -4 1
ToZero -3 -1
ToNegativeInf -4 1
ToNearestEven -4 1
ToPositiveInf -3 -1
1267650600228229401496703205376
297623
12
4
true -1 -1 1
d13f6370f96865df5dd54000000 jucotkuo7qe0hs0000000 false
1/2 0.50000 1 2
1.41421356237309504880168872420969807856967187537695
0.3333333333333333
0
25

Observations:

  • QuoRem truncates toward zero like Go’s / and % (-7/2 is -3, remainder -1); DivMod uses Euclidean division so the remainder is never negative (-4 and 1).

  • Int.Divide is new in Go 1.27. It takes an explicit RoundingMode (Trunc, Floor, Round or Ceil) and writes the quotient and the remainder together, so you no longer assemble floor or ceiling division from QuoRem and sign fix-ups. The remainder satisfies r = x - y*q for the chosen rounding. Round is the same constant as ToNearestEven; Trunc prints as ToZero, Floor as ToNegativeInf and Ceil as ToPositiveInf, which is why the output uses those names.

  • Exp(x, y, m) with a non-nil modulus is modular exponentiation; with nil it is plain x**y. GCD, ModInverse and ProbablyPrime(n) (which has a negligible false-positive chance for n = 20) are the number-theory basics.

  • Rat keeps results in lowest terms and prints 1/2; FloatString(5) rounds to five decimals. Use it for money-like quantities where float rounding is unacceptable.

  • A big.Float created with SetPrec(200) has 200 bits of mantissa; NewFloat gives 53 bits, which is float64 precision.

  • Formatting verbs: %d, %x, %s, %v work on *big.Int, which implements fmt.Formatter; print the pointer, not the struct.

  • Operations on big numbers allocate and are orders of magnitude slower than int64; use them only when the range requires it.

Big numbers also arise for correctness: if you only need to know that a 64-bit result would overflow, math/bits does it without allocation.

math/bits

math/bits has functions on uint8, uint16, uint32, uint64 and uint (suffix 8, 16, 32, 64 or none) that the compiler turns into single instructions on common CPUs:

Function Result

OnesCount, LeadingZeros, TrailingZeros, Len

Population count, zero counts at either end, and the number of bits needed (Len(0) == 0)

Reverse, ReverseBytes

Bit reversal and byte swap (endianness conversion)

RotateLeft(x, k)

Rotation; a negative k rotates right

Add64, Sub64

Sum or difference with carry or borrow in and out

Mul64, Div64, Rem64

Full 128-bit product, and division of a 128-bit value by a 64-bit one

UintSize

32 or 64

package main

import (
	"fmt"
	"math"
	"math/bits"
)

// mulChecked multiplies two uint64 values and reports overflow using bits.Mul64.
func mulChecked(a, b uint64) (uint64, bool) {
	hi, lo := bits.Mul64(a, b)
	return lo, hi != 0
}

// addChecked adds with carry detection.
func addChecked(a, b uint64) (uint64, bool) {
	sum, carry := bits.Add64(a, b, 0)
	return sum, carry != 0
}

// Set is a bitset for loan states 0..63.
type Set uint64

func (s Set) Has(i uint) bool { return s&(1<<i) != 0 }
func (s *Set) Add(i uint)     { *s |= 1 << i }
func (s Set) Len() int        { return bits.OnesCount64(uint64(s)) }

func main() {
	x := uint32(0b0010_1100)
	fmt.Println(bits.OnesCount32(x), bits.Len32(x), bits.LeadingZeros32(x), bits.TrailingZeros32(x))
	fmt.Printf("%08b %08b %08b\n", bits.Reverse8(0b0000_0110), bits.RotateLeft8(0b1000_0001, 1), bits.RotateLeft8(0b1000_0001, -1))
	fmt.Printf("%#x\n", bits.ReverseBytes32(0x01020304))

	fmt.Println(mulChecked(1<<32, 1<<31))
	fmt.Println(mulChecked(1<<32, 1<<32))
	fmt.Println(addChecked(math.MaxUint64, 1))

	// 128-bit division: (hi, lo) / y; panics if y <= hi.
	q, r := bits.Div64(1, 0, 3) // 2^64 / 3
	fmt.Println(q, r)

	// Next power of two and power-of-two test.
	n := uint(37)
	next := uint(1) << bits.Len(n-1)
	fmt.Println(next, bits.OnesCount(next) == 1, bits.UintSize)

	var s Set
	s.Add(3)
	s.Add(40)
	fmt.Println(s.Has(3), s.Has(4), s.Len(), bits.TrailingZeros64(uint64(s)))

	defer func() { fmt.Println("recovered:", recover()) }()
	bits.Div64(5, 0, 3) // y <= hi: quotient overflows
}
3 6 26 2
01100000 00000011 11000000
0x4030201
9223372036854775808 false
0 true
0 true
6148914691236517205 1
64 true 64
true false 2 3
recovered: runtime error: integer overflow

Typical uses are bitsets and flag sets (OnesCount is the size, TrailingZeros the lowest member), rounding up to a power of two (1 << bits.Len(n-1)), hash and checksum mixing with rotations, and checked unsigned arithmetic with Add64 and Mul64, where a non-zero carry or high word means overflow. Div64 panics when the quotient does not fit (y <= hi) or when y is zero.

Pseudo-random numbers: math/rand/v2

math/rand/v2 (Go 1.22) is the current package for non-cryptographic random numbers. It is the first "v2" in the standard library and fixes the design problems of the original math/rand.

math/rand (v1) math/rand/v2 Change

rand.Intn(n), Int31n, Int63n

rand.IntN(n), Int32N, Int64N, UintN, Uint32N, Uint64N

Consistent names; unsigned variants

none

rand.N(n) generic over any integer type, so rand.N(5 * time.Second) works

Go 1.22; also a method on *Rand in 1.27

rand.Seed(seed)

none

Seed is deprecated since 1.20 and a no-op since 1.24; seed a local generator instead

rand.NewSource(seed) (an Additive Lagged Fibonacci generator)

rand.NewPCG(s1, s2), rand.NewChaCha8([32]byte)

Better-defined algorithms with documented seeds

rand.Read

none

Deprecated; use crypto/rand.Read, or ChaCha8.Read for a deterministic stream

rand.Perm, rand.Shuffle

same

Unchanged

rand.NormFloat64, rand.ExpFloat64, rand.Zipf

same

Unchanged

The top-level functions (rand.IntN, rand.Float64, rand.Shuffle, …​) use a randomly seeded generator that the runtime provides; they are safe for concurrent use and need no setup. To get a reproducible sequence, build your own generator from a source:

  • rand.NewPCG(seed1, seed2): a small, very fast generator with 128 bits of state;

  • rand.NewChaCha8(seed): a ChaCha8-based generator with a 32-byte seed, designed to resist prediction. It is what the runtime uses for the top-level functions.

A *rand.Rand (made with rand.New(source)) is not safe for concurrent use. Give each goroutine its own, or guard it with a mutex.

package main

import (
	"fmt"
	"math/rand/v2"
	"slices"
	"time"
)

func main() {
	// A reproducible generator: PCG with an explicit two-word seed.
	r := rand.New(rand.NewPCG(1, 2))
	fmt.Println(r.IntN(100), r.IntN(100), r.IntN(100))
	fmt.Println(r.Float64())
	fmt.Println(r.Perm(5))

	// Same seed, same sequence.
	r2 := rand.New(rand.NewPCG(1, 2))
	fmt.Println(r2.IntN(100), r2.IntN(100), r2.IntN(100))

	// ChaCha8 takes a 32-byte seed.
	seed := [32]byte([]byte("shelf-test-seed-0123456789abcdef"))
	c := rand.New(rand.NewChaCha8(seed))
	fmt.Println(c.Uint64(), c.IntN(10))

	// Shuffle in place.
	titles := []string{"Dune", "Emma", "Ulysses", "Dubliners", "Persuasion"}
	rand.New(rand.NewPCG(7, 7)).Shuffle(len(titles), func(i, j int) {
		titles[i], titles[j] = titles[j], titles[i]
	})
	fmt.Println(titles)

	// Go 1.27: N is also a method on *Rand, generic over any integer type,
	// so a Duration needs no conversion.
	d := r.N(10 * time.Second)
	fmt.Println(d >= 0 && d < 10*time.Second)

	rr := rand.New(rand.NewPCG(1, 2))
	fmt.Println(rr.N(int64(1000)), rr.N(uint8(200)), rr.N(time.Hour))

	// The top-level functions use a randomly seeded generator: values differ per run.
	a, b := rand.IntN(1<<30), rand.IntN(1<<30)
	fmt.Println(a >= 0, b >= 0, rand.N(time.Minute) < time.Minute)

	// Weighted/other distributions: NormFloat64, ExpFloat64, Zipf.
	z := rand.NewZipf(rand.New(rand.NewPCG(3, 4)), 1.5, 2, 100)
	counts := make([]int, 4)
	for range 1000 {
		counts[min(z.Uint64(), 3)]++
	}
	fmt.Println(counts, slices.Max(counts) > 0)

	defer func() { fmt.Println("recovered:", recover()) }()
	rand.IntN(0)
}
76 61 78
0.4297927436037299
[2 3 4 0 1]
76 61 78
15734305148095224245 3
[Dune Emma Dubliners Ulysses Persuasion]
true
769 123 47m3.940805972s
true true true
[229 121 101 549] true
recovered: invalid argument to IntN

Every line of this output is the same on every run: the sequences for a given seed are fixed, which is what makes seeded generators useful for tests and simulations. The top-level functions (rand.IntN, rand.N) are randomly seeded, so the program prints only booleans about them instead of their values.

Notes:

  • IntN(n), Int64N(n), rand.N(n) and their kin panic when n <= 0 (the last line prints the message).

  • IntN(n) is uniform: the package avoids the modulo bias of rand.Int() % n.

  • Perm(n) returns a random permutation of 0..n-1; Shuffle(n, swap) shuffles anything with a swap function, such as the titles above. Prefer these over hand-written Fisher-Yates loops.

  • Zipf generates values with the Zipf distribution, used to model skewed access patterns when benchmarking caches.

  • Never use math/rand for secrets. The package documentation says explicitly that its output might be easily predictable regardless of how it is seeded, and that ChaCha8 is resistant to prediction while PCG is not suited to security work. Passwords, tokens, session identifiers and keys use crypto/rand.

Go 1.27: N as a method, and the stdversion check

Go 1.27 added generic methods to the language (a method can have its own type parameters, though interfaces cannot). The first standard-library use is (*Rand).N, which matches the top-level rand.N. The call rr.N(time.Hour) in the program above returns a time.Duration directly.

The go line in go.mod selects language features; new library functions are not hidden by it, but go vet reports a call to a standard-library symbol that is newer than the module’s go version. In a module that says go 1.26:

r := rand.New(rand.NewPCG(1, 2))
fmt.Println(r.N(10), uuid.New())
main.go:11:16: rand.(*Rand).N requires go1.27 or later (module is go1.26)
main.go:11:28: uuid.New requires go1.27 or later (module is go1.26)

The stdversion analyzer of go vet does this check, and it applies equally to Int.Divide, maphash.Hasher and the uuid package below. See Toolchain Versions and Compatibility for the go and toolchain lines.

Misuse of the type parameter is a compile error that lists the allowed types:

var n int = 10
fmt.Println(rand.N(n), rand.IntN(int64(5)), rand.N(2.5))
./main.go:10:35: cannot use int64(5) (constant 5 of type int64) as int value in argument to rand.IntN
./main.go:10:52: float64 does not satisfy rand.intType (float64 missing in ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr)

crypto/rand

crypto/rand reads from the operating system’s cryptographically secure generator (getrandom(2) on Linux, arc4random_buf on macOS, ProcessPrng on Windows). It has no seed and no reproducibility; that is the point.

Function Use

rand.Text()

A random string of at least 128 bits, in the base32 alphabet (A-Z, 2-7): tokens, passwords, identifiers. Go 1.24

rand.Read(b)

Fills b with random bytes. Since Go 1.24 it never returns an error: if the system source fails, the program crashes

rand.Int(rand.Reader, max)

Uniform *big.Int in [0, max)

rand.Prime(rand.Reader, bits)

A random prime of the given bit length

rand.Reader

The shared io.Reader, safe for concurrent use

package main

import (
	crand "crypto/rand"
	"encoding/hex"
	"fmt"
	"math/big"
	"math/rand/v2"
	"regexp"
)

func main() {
	// A session or API token: use crypto/rand, never math/rand.
	token := crand.Text()
	fmt.Println(len(token), regexp.MustCompile(`^[A-Z2-7]+$`).MatchString(token))

	// Raw random bytes; Read never returns an error (it crashes the program instead).
	key := make([]byte, 16)
	crand.Read(key)
	fmt.Println(len(hex.EncodeToString(key)))

	// A uniform integer in [0, max) of any size.
	n, err := crand.Int(crand.Reader, big.NewInt(1000))
	fmt.Println(n.Cmp(big.NewInt(1000)) < 0, err)

	// Seed a fast, non-cryptographic generator from the OS source when you want
	// per-run variation but your own generator instance (e.g. one per worker).
	var seed [32]byte
	crand.Read(seed[:])
	local := rand.New(rand.NewChaCha8(seed))
	fmt.Println(local.IntN(6)+1 >= 1)

	// The ChaCha8 generator is also what the top-level math/rand/v2 functions use,
	// seeded by the runtime. It is fast, but its output is predictable from its state.
}
26 true
32
true <nil>
true

The program prints properties (lengths, a character-set check) rather than values because the values differ on every run. The two packages are imported as crand and rand to avoid the name clash.

Need Use Why

Session token, API key, password reset link, salt, nonce

crypto/rand (Text or Read)

Unpredictable even if the attacker has seen earlier outputs

Shuffle a playlist, pick a sample, jitter a retry delay

math/rand/v2 top-level functions

Fast, concurrency-safe, no setup

Reproducible simulation or test data

rand.New(rand.NewPCG(a, b)) or ChaCha8 with a fixed seed

The sequence is stable across runs and releases

Uniform integer below a big bound

crypto/rand.Int

No modulo bias on arbitrary sizes

Do not reduce crypto/rand bytes with % to get a number in a range: the result is biased. Use crypto/rand.Int, or rejection sampling. The cryptographic hash, MAC and key-derivation side is on Crypto and Security.

uuid (Go 1.27)

Go 1.27 adds the package uuid for RFC 9562 universally unique identifiers. Its import path is just "uuid": like fmt or slices, it is part of the standard library, with no domain prefix, so it needs no entry in go.mod. It was proposed in issue 62026 and is documented at pkg.go.dev/uuid. (The popular third-party github.com/google/uuid module is a separate package with a larger API; code that imports it keeps working.) The local toolchain’s go doc uuid and $GOROOT/src/uuid confirm the contents described here.

Because uuid is in the standard library, the module file needs nothing but the language version:

module example.com/shelf

go 1.27

The API is deliberately small.

Member What it does

type UUID [16]byte

A UUID is a 16-byte array: comparable with ==, usable as a map key, copied by value

uuid.New()

A new UUID using the algorithm "suitable for most purposes"; currently the same as NewV4, but the doc reserves the right to change it

uuid.NewV4()

Version 4: 122 random bits from a cryptographically secure source

uuid.NewV7()

Version 7: a 48-bit Unix millisecond timestamp in the high bits, at least 62 random bits; UUIDs sort in increasing order (unless the clock moves backwards)

uuid.Parse(s)

Accepts xxxxxxxx-xxxx-..., the same in braces, the urn:uuid: form, and 32 hex digits with no dashes, in any letter case

uuid.MustParse(s)

Like Parse but panics on bad input: for package-level constants and tests

uuid.Nil(), uuid.Max()

The all-zero and all-ones special UUIDs of RFC 9562 sections 5.9 and 5.10; Nil() is not the Go nil

(UUID).String()

Lowercase hex-and-dash form

(UUID).Compare(v)

-1, 0 or +1 in big-endian byte order (RFC 9562 section 6.11), usable directly as slices.SortFunc(ids, uuid.UUID.Compare)

MarshalText, AppendText, UnmarshalText

encoding.TextMarshaler, TextAppender and TextUnmarshaler, which makes encoding/json, encoding/xml and flag.TextVar treat a UUID as a string

There are no constructors for versions 1, 3, 5, 6 or 8 (the name-based and time-and-MAC variants), no Version() or Variant() accessor, no database/sql Scanner/Valuer, and no exported error value for a parse failure. Where you need those, store id.String() in the database column (or the 16 bytes as id[:]), read the version from id[6]>>4, and treat parse errors as opaque.

package main

import (
	"encoding/json"
	"fmt"
	"slices"
	"uuid"
)

// Member is a library member identified by a UUID.
type Member struct {
	ID   uuid.UUID `json:"id"`
	Name string    `json:"name"`
}

func main() {
	id := uuid.New()                                  // currently the same algorithm as NewV4
	fmt.Println(len(id.String()), id[6]>>4, id[8]>>6) // version nibble and variant bits

	v7a, v7b, v7c := uuid.NewV7(), uuid.NewV7(), uuid.NewV7()
	fmt.Println(v7a[6]>>4, v7a.Compare(v7b), v7b.Compare(v7c))
	ids := []uuid.UUID{v7c, v7a, v7b}
	slices.SortFunc(ids, uuid.UUID.Compare)
	fmt.Println(slices.Equal(ids, []uuid.UUID{v7a, v7b, v7c}))

	// Parse accepts four forms, any letter case.
	for _, s := range []string{
		"f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
		"{F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6}",
		"urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
		"f81d4fae7dec11d0a76500a0c91e6bf6",
	} {
		u, err := uuid.Parse(s)
		fmt.Println(u, err)
	}
	_, err := uuid.Parse("not-a-uuid")
	fmt.Println(err)

	fmt.Println(uuid.Nil(), uuid.Max(), uuid.Nil() == uuid.UUID{})

	// UUID implements encoding.TextMarshaler/TextUnmarshaler, so JSON uses strings.
	m := Member{ID: uuid.MustParse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"), Name: "Ann"}
	data, _ := json.Marshal(m)
	fmt.Println(string(data))
	var back Member
	fmt.Println(json.Unmarshal(data, &back), back == m)

	// Being an array, a UUID is comparable and can be a map key.
	byID := map[uuid.UUID]Member{m.ID: m}
	fmt.Println(byID[m.ID].Name)

	defer func() { fmt.Println("recovered:", recover()) }()
	uuid.MustParse("nope")
}
36 4 2
7 -1 -1
true
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
invalid uuid
00000000-0000-0000-0000-000000000000 ffffffff-ffff-ffff-ffff-ffffffffffff true
{"id":"f81d4fae-7dec-11d0-a765-00a0c91e6bf6","name":"Ann"}
<nil> true
Ann
recovered: invalid uuid

Source: uuid package

The two small numbers on the first line are the version nibble (4 for New) and the variant bits (binary 10, shown as 2); on the second line, NewV7 returns version 7 and each call compares lower than the next (-1), which Compare confirms. The JSON round trip works because of the text-marshaling methods, so a Member can be sent to cmd/shelfd clients as a plain string. The MustParse("nope") at the end panics with the message invalid uuid; because the error value is unexported, test for failure with err != nil only.

Choosing between version 4 and version 7

Version 4 (New, NewV4) Version 7 (NewV7)

Content

122 random bits

Millisecond timestamp (plus a sub-millisecond fraction in the current implementation) and random bits

Ordering

None; random

Time-ordered, so new rows land at the end of a B-tree index

Leaks

Nothing

The creation time (readable by anyone holding the ID)

Good for

Opaque identifiers, tokens that must not reveal when they were made

Database primary keys, log and event identifiers, anything sorted or paginated by creation

A random v4 key scatters inserts across the whole index of a table, which hurts write locality on large tables; v7 appends. If the creation time must not leak, use v4. A UUID is an identifier, not a secret: a v7 UUID is partly predictable, and even a v4 should not be your only protection for access to a resource. For security tokens use crypto/rand.Text. The program below reads the timestamp back out of a v7 value:

package main

import (
	"fmt"
	"time"
	"uuid"
)

// createdAt extracts the millisecond timestamp stored in a version 7 UUID.
func createdAt(u uuid.UUID) (time.Time, bool) {
	if u[6]>>4 != 7 {
		return time.Time{}, false
	}
	var ms int64
	for _, b := range u[:6] {
		ms = ms<<8 | int64(b)
	}
	return time.UnixMilli(ms), true
}

func main() {
	before := time.Now().Add(-time.Second)
	id := uuid.NewV7()
	t, ok := createdAt(id)
	fmt.Println(ok, t.After(before), time.Since(t) < time.Minute)

	_, ok = createdAt(uuid.NewV4())
	fmt.Println(ok)

	// A fixed v7 value from RFC 9562 appendix A.6 style layout.
	fixed := uuid.MustParse("017f22e2-79b0-7cc3-98c4-dc0c0c07398f")
	t, ok = createdAt(fixed)
	fmt.Println(ok, t.UTC().Format(time.RFC3339))
}
true true true
false
true 2022-02-22T19:22:22Z

The fixed value in the last call is the UUIDv7 example from RFC 9562, whose first 48 bits are 1645557742000 milliseconds, which is 22 February 2022. See encoding/json for how TextMarshaler values are encoded, and database/sql for storing identifiers.

hash/maphash

hash/maphash hashes byte sequences and comparable values to a uint64 with a seeded, collision-resistant function. It is the building block for your own hash tables, Bloom filters and sharding. It is not cryptographic and not stable: a Seed exists only in one process and cannot be saved, so never store or compare hashes across runs.

  • maphash.MakeSeed() creates a random Seed. Give each data structure its own seed; a different seed is a different hash function, which defeats hash-flooding denial-of-service attacks.

  • maphash.String(seed, s) and maphash.Bytes(seed, b) hash text and bytes in one call.

  • maphash.Comparable(seed, v) (Go 1.24) hashes any comparable value so that v1 == v2 implies equal hashes.

  • maphash.Hash is the incremental form: SetSeed, Write, WriteString, WriteByte, Sum64, Reset, and WriteComparable for non-byte data. It is not safe for concurrent use (a Seed is).

  • Go 1.27: the interface maphash.Hasher[T] (methods Hash(*Hash, T) and Equal(x, y T) bool) is the contract between a hash-based container and its elements, and maphash.ComparableHasher[T] is the ready-made implementation for comparable types that uses ==. A Hasher can define an equivalence other than ==: case-insensitive strings, pointers compared by their fields, even types that are not comparable.

The contract has two rules: values that are Equal must produce the same hash, and a Hasher must be stateless. The program below builds a minimal generic hash set on top of the interface and plugs in a case-insensitive hasher:

package main

import (
	"fmt"
	"hash/maphash"
	"strings"
)

// CaseInsensitive is a maphash.Hasher[string] that ignores letter case.
type CaseInsensitive struct{}

func (CaseInsensitive) Hash(h *maphash.Hash, s string) { h.WriteString(strings.ToLower(s)) }
func (CaseInsensitive) Equal(x, y string) bool         { return strings.ToLower(x) == strings.ToLower(y) }

// HashSet is a minimal open-hashing set built on any maphash.Hasher.
type HashSet[T any] struct {
	hasher  maphash.Hasher[T]
	seed    maphash.Seed
	buckets map[uint64][]T
}

func NewHashSet[T any](h maphash.Hasher[T]) *HashSet[T] {
	return &HashSet[T]{hasher: h, seed: maphash.MakeSeed(), buckets: map[uint64][]T{}}
}

func (s *HashSet[T]) sum(v T) uint64 {
	var h maphash.Hash
	h.SetSeed(s.seed)
	s.hasher.Hash(&h, v)
	return h.Sum64()
}

func (s *HashSet[T]) Add(v T) bool {
	k := s.sum(v)
	for _, e := range s.buckets[k] {
		if s.hasher.Equal(e, v) {
			return false
		}
	}
	s.buckets[k] = append(s.buckets[k], v)
	return true
}

func (s *HashSet[T]) Has(v T) bool {
	for _, e := range s.buckets[s.sum(v)] {
		if s.hasher.Equal(e, v) {
			return true
		}
	}
	return false
}

type ISBNKey struct {
	Prefix string
	Number int
}

func main() {
	seed := maphash.MakeSeed()
	h1 := maphash.String(seed, "Dune")
	h2 := maphash.Bytes(seed, []byte("Dune"))
	fmt.Println(h1 == h2, h1 == maphash.String(seed, "dune"))

	// Comparable hashes any comparable value consistently with ==.
	k := ISBNKey{"978", 441013593}
	fmt.Println(maphash.Comparable(seed, k) == maphash.Comparable(seed, ISBNKey{"978", 441013593}))

	// A different seed is a different hash function (hash-flooding resistance),
	// and a seed cannot be saved: the numbers differ from run to run.
	fmt.Println(maphash.String(seed, "Dune") == maphash.String(maphash.MakeSeed(), "Dune"))

	// Incremental hashing: chunks do not matter.
	var a, b maphash.Hash
	a.SetSeed(seed)
	b.SetSeed(seed)
	a.WriteString("Dune")
	b.WriteString("Du")
	b.WriteString("ne")
	fmt.Println(a.Sum64() == b.Sum64())

	// Hasher (Go 1.27) lets containers plug in their own equivalence.
	set := NewHashSet[string](CaseInsensitive{})
	fmt.Println(set.Add("Dune"), set.Add("DUNE"), set.Add("Emma"), set.Has("emma"), set.Has("Ulysses"))

	keys := NewHashSet[ISBNKey](maphash.ComparableHasher[ISBNKey]{})
	fmt.Println(keys.Add(k), keys.Add(k), keys.Has(ISBNKey{"978", 1}))
}
true false
true
false
true
true false true true false
true false false

The first four lines show the basic guarantees: the string and bytes forms agree, hashing is case-sensitive, a different seed gives a different result, and chunked writes hash like one write. HashSet.Add returns false for "DUNE" because the hasher treats it as equal to "Dune". The built-in map cannot do this: it always uses == and comparable keys. A generic standard-library hash table that accepts a Hasher does not exist yet; the interface is the extension point that later containers and third-party modules are expected to share.

unique

unique.Make(v) returns a unique.Handle[T] for a comparable value v. Two handles are equal if and only if the values are equal, and comparing handles is a pointer comparison, not a comparison of the (possibly long) values. h.Value() returns a copy of the original. This is interning (canonicalizing): many equal strings or structs share one stored copy. The unique values are kept in a global table that does not keep them alive when no handle refers to them anymore.

package main

import (
	"fmt"
	"strings"
	"unique"
	"unsafe"
)

// Genre is interned: copies are one pointer-sized handle, and comparing two
// genres is a pointer comparison instead of a string comparison.
type Genre = unique.Handle[string]

type Book struct {
	Title string
	Genre Genre
}

func main() {
	genres := []string{"Science Fiction", "science fiction", "Novel"}
	var books []Book
	for i, g := range genres {
		books = append(books, Book{Title: fmt.Sprint("book", i), Genre: unique.Make(g)})
	}
	books = append(books, Book{Title: "book3", Genre: unique.Make(strings.Clone("Science Fiction"))})

	fmt.Println(books[0].Genre == books[1].Genre) // different strings, different handles
	fmt.Println(books[0].Genre == books[3].Genre) // same value, same handle
	fmt.Println(books[0].Genre.Value(), unsafe.Sizeof(books[0].Genre))
}
false
true
Science Fiction 8

Source: unique package

A handle is a single pointer (8 bytes on 64-bit platforms) regardless of the size of T, so a field like Genre costs less than a string header (16 bytes) and compares in one instruction. The trade-off is a map lookup per Make, so interning pays off for values that repeat a lot and are compared often (genres, host names, log keys, net/netip for IPv6 zone names). For values that are mostly distinct, it only adds cost. How the cleanup of unreferenced entries works, and the sibling packages weak and runtime.AddCleanup, are on Cleanups, Weak Pointers and Interning.

Where the books are outdated or wrong

  • Bodner (Learning Go, chapter 3) predates the slices, maps and cmp packages, so the hand-written loops for copying, comparing, deleting and ordered keys, and the sort.Slice idiom, have a one-line replacement today (slices.Clone, slices.Equal, slices.Sort, slices.Sorted(maps.Keys(m))). Its chapter-15 discussion of what generics would bring is answered by these packages and by iter.Seq (see Iterators and Range-over-Func).

  • Both books describe the math/rand default generator as having a fixed seed that you must replace with rand.Seed. Since Go 1.20 the default generator is seeded randomly, rand.Seed has been deprecated since then and has been a no-op since Go 1.24 (unless GODEBUG=randseednop=0), and new code uses math/rand/v2 (Go 1.22): rand.IntN, rand.N, and for reproducibility a local rand.New(rand.NewPCG(a, b)). For secrets, the books' mention of crypto/rand stands, and rand.Text (Go 1.24) now covers token generation in one call.

  • Vivien (Learning Go Programming) predates math.FMA, the generic slices, maps and cmp helpers, math/rand/v2 and, in math/big, the Int.Divide rounding-mode method (1.27). The width of int is not 32 bits on 64-bit platforms (it has been 64 bits there since Go 1.1); math.MaxInt and bits.UintSize report the real size.

  • Neither book covers unique (1.23), maphash.Comparable (1.24), the maphash.Hasher interface (1.27) or the new uuid package (1.27). Before Go 1.27 the usual answer for UUIDs was a third-party module; code that still imports one keeps working, and migrating is a type change because the two UUID types are unrelated arrays of 16 bytes.

  • Code that sorts a plain slice with a hand-written sort.Interface type, or with sort.Slice, is superseded by slices.SortFunc with cmp.Compare.

Version notes

Go release Change

1.19

sort.Find; maphash.String and maphash.Bytes (the package itself is from 1.14)

1.20

math/rand is seeded randomly by default; rand.Seed deprecated

1.21

slices, maps and cmp packages; min, max and clear builtins

1.22

math/rand/v2; cmp.Or; slices.Concat; slices.Delete family zeroes vacated elements; sort.Ints and friends call slices.Sort

1.23

Iterator functions in slices and maps (All, Values, Collect, Sorted, Chunk, Repeat); the unique package

1.24

crypto/rand.Text; crypto/rand.Read never fails; maphash.Comparable and WriteComparable; rand.Seed is a no-op

1.25

maphash.Hash.Clone

1.27

uuid package; maphash.Hasher and ComparableHasher; big.Int.Divide with rounding modes; (*rand.Rand).N (the first generic method in the standard library)