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.
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.
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.