The maps package

You have the map type from the previous lessons. The maps package, added in Go 1.21, is the equivalent of slices for maps: a handful of generic helpers that replace a few lines of hand-rolled code.

The functions you reach for

maps.Clone(m)                                     // shallow copy
maps.Equal(a, b)                                  // element-by-element equality
maps.Copy(dst, src)                               // merge src into dst (overwrites)
maps.Keys(m)                                      // iterator over keys
maps.Values(m)                                    // iterator over values
maps.DeleteFunc(m, predicate)                     // remove matching entries

That is most of them.

Keys and Values: iterators, not slices

In Go 1.21 and 1.22, maps.Keys returned []K. From 1.23 on it returns iter.Seq[K], the new iterator type. To get a slice of keys, the modern idiom is to materialise the iterator with slices.Collect:

m := map[string]int{"a": 1, "b": 2, "c": 3}

keys := slices.Collect(maps.Keys(m))
slices.Sort(keys)                       // [a b c]

The Sort step is the standard "I want predictable order from a map" pattern. Map iteration order is randomised, so anything you print straight from a map will differ from run to run.

The classic sort-by-key print

m := map[string]int{"banana": 2, "apple": 5, "cherry": 1}

keys := slices.Collect(maps.Keys(m))
slices.Sort(keys)

for _, k := range keys {
    fmt.Println(k, m[k])
}

This pattern shows up every time you want to display, log, or serialise a map in a stable order. It is short, generic, and works for any map type.

Clone for safe mutation

clone := maps.Clone(m)
clone["new"] = 42       // does not affect m

Clone is shallow: keys and values are copied as-is. If your value type is a slice, pointer, or another map, the underlying memory is still shared. For deep clones, walk the structure yourself.

Equal for testing

maps.Equal(a, b)                 // values compared with ==
maps.EqualFunc(a, b, fn)         // values compared with fn

Use Equal when value types are comparable (==). Use EqualFunc when values are slices, structs containing slices, or anything else that does not satisfy comparable.

Copy and DeleteFunc

defaults := map[string]int{"timeout": 30, "retries": 3}
overrides := map[string]int{"timeout": 60}

merged := maps.Clone(defaults)
maps.Copy(merged, overrides)     // {"timeout": 60, "retries": 3}

Copy(dst, src) writes every entry from src into dst. Existing keys in dst are overwritten. This is the standard "defaults plus overrides" merge.

maps.DeleteFunc(m, func(k string, v int) bool {
    return v == 0           // drop zero-valued entries
})

DeleteFunc removes entries where the predicate returns true. Avoids the awkward "collect keys to delete, then delete them" two-pass pattern you needed before generics.

Map iteration is randomised on purpose

Go intentionally randomises the order of for k := range m iterations to discourage code from depending on insertion order. If you need a deterministic order, materialise the keys with slices.Collect(maps.Keys(m)), sort them, then iterate. Do not rely on accidental ordering; it will bite you when you upgrade Go versions or hit a different map size.

Task

The challenge on the right walks you through materialising a map's keys, sorting them, and printing entries in a stable order. Run it locally with go run . and compare your output to the expected block.