Generic Stdlib: slices, maps and cmp

Reviewed & published by Brayan K

Go 1.21 brought generic helper packages that replace a lot of hand-written loops. You'll use slices (Contains, Sort, SortFunc, BinarySearch, Clone, Equal, Max/Min), maps (Keys, Values, Clone, Equal), and cmp (Compare, Or, Ordered) — and write your own generic function with a constraint.

Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Searching and sorting with slices

The slices package gives you Contains, Index, Sort, BinarySearch, and Max/Min for any element type. Note BinarySearch requires a sorted slice.

package main

import (
    "fmt"
    "slices"
)

func main() {
    nums := []int{5, 2, 8, 2, 1}

    // Membership and position
    fmt.Println(slices.Contains(nums, 8)) // true
    fmt.Println(slices.Index(nums, 2))    // 1 (first match), -1 if absent

    // Sort in place (ascending) for an ordered type
    slices.Sort(nums)
    fmt.Println(nums)                     // [1 2 2 5 8]

    // BinarySearch needs a SORTED slice; returns index + found bool
    i, found := slices.BinarySearch(nums, 5)
    fmt.Println(i, found)                 // 3 true

    // Max / Min over the elements (Go 1.21+)
    fmt.Println(slices.Max(nums), slices.Min(nums)) // 8 1
}

2️⃣ Custom ordering with SortFunc and cmp

To sort structs or use multiple keys, slices.SortFunc takes a comparison returning <0, 0, or >0. Build it from cmp.Compare and chain tiebreaks with cmp.Or.

package main

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

type Person struct {
    Name string
    Age  int
}

func main() {
    people := []Person{
        {"Ada", 36}, {"Bo", 24}, {"Cy", 36},
    }

    // SortFunc takes a cmp-style comparison: <0, 0, or >0.
    // Sort by Age, then by Name as a tiebreaker, using cmp.Or.
    slices.SortFunc(people, func(a, b Person) int {
        return cmp.Or(
            cmp.Compare(a.Age, b.Age),   // primary key
            cmp.Compare(a.Name, b.Name), // tiebreak: first non-zero wins
        )
    })
    fmt.Println(people)

    // Clone makes an independent copy; Equal compares element by element.
    backup := slices.Clone(people)
    fmt.Println(slices.Equal(people, backup)) // true
}

3️⃣ The maps package

maps.Keys and maps.Values give you iterators over a map; collect them with slices.Collect and sort for deterministic output. maps.Clone and maps.Equal round it out.

package main

import (
    "fmt"
    "maps"
    "slices"
)

func main() {
    prices := map[string]int{"pen": 2, "pad": 5, "ink": 9}

    // maps.Keys returns an iterator (Go 1.23+). Collect it into a slice,
    // then sort for deterministic output (map order is random).
    keys := slices.Collect(maps.Keys(prices))
    slices.Sort(keys)
    fmt.Println(keys) // [ink pad pen]

    // Clone a map (shallow copy) and compare with maps.Equal.
    copyOf := maps.Clone(prices)
    fmt.Println(maps.Equal(prices, copyOf)) // true

    // Sum the values via the Values iterator.
    total := 0
    for v := range maps.Values(prices) {
        total += v
    }
    fmt.Println(total) // 16
}

4️⃣ Writing your own generic function

When the stdlib doesn't have what you need, write a generic function with a type parameter and a constraint. cmp.Ordered allows any type that supports < and >.

package main

import (
    "cmp"
    "fmt"
)

// Your own generic helper. The cmp.Ordered constraint allows any type
// that supports < and >, so this works for ints, floats, and strings.
func MaxOf[T cmp.Ordered](xs []T) T {
    best := xs[0]
    for _, x := range xs[1:] {
        if x > best {
            best = x
        }
    }
    return best
}

func main() {
    fmt.Println(MaxOf([]int{3, 9, 4}))           // 9
    fmt.Println(MaxOf([]string{"go", "rust"}))   // rust
    fmt.Println(MaxOf([]float64{1.5, 0.5, 2.5})) // 2.5
}

🎯 Your Turn

Use the slices package. Fill in the two blanks marked ___ to test membership and sort the words.

package main

import (
    "fmt"
    "slices"
)

func main() {
    words := []string{"pear", "apple", "fig", "apple"}

    // 🎯 YOUR TURN — fill in the blanks marked ___

    // 1) Is "fig" in the slice?
    fmt.Println(slices.___(words, "fig"))  // 👉 Contains  -> true

    // 2) Sort the words alphabetically in place
    slices.___(words)                      // 👉 Sort

    fmt.Println(words) // [apple apple fig pear]

    // ✅ Run it free at the Go Playground (needs Go 1.21+).
}

Common Errors (and the fix)

❌ Calling BinarySearch on an unsorted slice — wrong result.

✅ slices.Sort first, or use slices.Contains for unsorted data.

❌ Returning a bool from a SortFunc — it wants an int.

✅ Return cmp.Compare(a, b) (or <0/0/>0).

❌ Forgetting map order is random when printing keys.

✅ slices.Sort the collected keys for stable output.

❌ Mutating a slice you handed out — the caller's view changes too.

✅ Store or return a slices.Clone for an independent copy.

🧠 Quick Recall

-1. It returns -1, 0, or +1 for less/equal/greater — exactly what SortFunc expects.

constraint for orderable types = ?

cmp.Ordered. Use comparable only when you need ==/!= rather than </>.

🎯 Mini-Challenge: Rank a Leaderboard

Sort scores by points descending, breaking ties by player name ascending, using slices.SortFunc with cmp.Compare and cmp.Or.

package main

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

type Score struct {
    Player string
    Points int
}

// 🎯 MINI-CHALLENGE: rank a leaderboard.
// 1. Sort scores by Points DESCENDING (highest first).
//    Hint: cmp.Compare(b.Points, a.Points) flips the order.
// 2. Break ties by Player name ASCENDING using cmp.Or.
// 3. Print the sorted slice.
//
// ✅ Expected: highest points first, name as tiebreaker.

func main() {
    scores := []Score{{"Bo", 7}, {"Ada", 9}, {"Cy", 7}}
    _ = slices.SortFunc[[]Score]
    _ = cmp.Or[int]
    fmt.Println(scores)
}

🎉 Lesson Complete!

Practice quiz

Which standard library package gained generic helpers in Go 1.21?

  • slices
  • container/list
  • sort (only)
  • reflect

Answer: slices. Go 1.21 promoted slices and maps (and added cmp) to the standard library as generic helper packages.

What does slices.Contains(s, v) return?

  • the index of v
  • a sorted copy
  • a bool: whether v is present in s
  • the length of s

Answer: a bool: whether v is present in s. slices.Contains reports whether the value v appears in the slice; slices.Index returns its position (or -1).

How do you sort a slice of ints in ascending order with the slices package?

  • slices.Clone(s)
  • slices.Sort(s)
  • slices.Order(s)
  • slices.Reverse(s)

Answer: slices.Sort(s). slices.Sort sorts a slice of an ordered type in place; slices.SortFunc takes a custom comparison.

What does slices.SortFunc require its comparison function to return?

  • a bool
  • the sorted slice
  • an error
  • a negative, zero, or positive int for less/equal/greater

Answer: a negative, zero, or positive int for less/equal/greater. SortFunc uses a cmp-style function returning <0, 0, or >0; cmp.Compare is the usual building block.

Which function requires the slice to already be sorted to work correctly?

  • slices.BinarySearch
  • slices.Clone
  • slices.Equal
  • slices.Contains

Answer: slices.BinarySearch. slices.BinarySearch assumes a sorted slice and returns the position plus a found bool in O(log n).

What does maps.Keys return in Go 1.23+?

  • a sorted []K
  • an iterator (iter.Seq[K]) over the map's keys
  • the number of keys
  • the map itself

Answer: an iterator (iter.Seq[K]) over the map's keys. In Go 1.23 maps.Keys/Values return iterators (iter.Seq); collect them with slices.Collect if you need a slice.

What does cmp.Or(a, b, c) do?

  • adds the values
  • returns the first non-zero argument (or the zero value)
  • compares two values
  • sorts the arguments

Answer: returns the first non-zero argument (or the zero value). cmp.Or returns its first argument that is not the zero value, handy for fallback/default chains.

What does cmp.Compare(a, b) return when a < b?

  • a positive number
  • true
  • 0
  • a negative number

Answer: a negative number. cmp.Compare returns -1 if a<b, 0 if equal, +1 if a>b — exactly what SortFunc and BinarySearchFunc expect.

Which constraint lets a generic function accept any orderable type?

  • error
  • any
  • cmp.Ordered
  • comparable

Answer: cmp.Ordered. cmp.Ordered constrains to types supporting < <= >= > (ints, floats, strings); comparable only allows == and !=.

What do slices.Equal and maps.Equal compare?

  • memory addresses
  • whether the elements (or key/value pairs) are equal
  • the capacity
  • only the lengths

Answer: whether the elements (or key/value pairs) are equal. slices.Equal compares element by element; maps.Equal compares the same set of keys mapped to equal values.

Continue this course

Frequently asked questions

What is the slices package and when was it added?

slices is a generic standard-library package (promoted in Go 1.21) of helpers that work on a slice of any element type via type parameters. It covers the operations you used to write by hand: Contains, Index, Sort, SortFunc, BinarySearch, Equal, Clone, Reverse, Insert, Delete, and Max/Min. Because it is generic, one Sort works for []int, []string, []float64, and more.

How is slices.Sort different from slices.SortFunc?

slices.Sort works on slices whose element type satisfies cmp.Ordered (ints, floats, strings) and sorts ascending in place. slices.SortFunc lets you supply a comparison function returning a negative, zero, or positive int — use it to sort structs, sort by a field, sort descending, or combine keys. cmp.Compare and cmp.Or are the usual building blocks for that function.

What does the cmp package give me?

cmp provides the glue for ordering generic values: cmp.Ordered is the constraint for types that support the < and > operators; cmp.Compare(a, b) returns -1, 0, or +1; and cmp.Or returns the first non-zero argument, which is perfect for tiebreaking (compare the primary key, then the secondary). cmp.Less is also available as a boolean comparison.

Why do maps.Keys and maps.Values return iterators now?

Since Go 1.23 they return range-over-func iterators (iter.Seq), so you can write for k := range maps.Keys(m) without allocating a slice. If you actually need a slice, collect the iterator with slices.Collect(maps.Keys(m)). Map iteration order is randomized, so sort the collected keys when you need deterministic output. The maps package also offers Clone and Equal.

What's the catch with slices.Clone and shared backing arrays?

Slices are views over a shared backing array, so passing a slice around and then appending or editing it can surprise callers who hold the original. slices.Clone returns a fresh, independent shallow copy (the elements themselves are not deep-copied) so later mutations don't bleed across. Use it whenever you store or return a slice that the caller might keep and you might still modify.

When should I write my own generic function instead of using these?

Reach for the standard packages first — they're tested, fast, and idiomatic. Write your own generic function when you need behavior they don't cover, for example a Map/Filter/Reduce helper or a domain-specific transform. Use a type parameter like [T any] for general work and constrain it (e.g. [T cmp.Ordered] or [T comparable]) only when you actually need ordering or equality on the type.

Related lessons