Last Updated: May 17, 2026
Go 1.21 added a standard-library package called maps that gives you ready-made versions of the operations people kept hand-rolling: cloning, comparing, merging, and pruning entries by predicate. Go 1.23 extended it with iterator-based helpers (Keys, Values, All, Insert, Collect) that plug directly into the new range-over-function form. The import path is simply import "maps", and the package lives in the standard library, no module dependency required.
maps.Clone returns a shallow copy of a map. The new map has its own internal buckets, so writes to one no longer touch the other.
Writing through snapshot doesn't touch catalog because they no longer share storage. This is the standard way to take a point-in-time snapshot of a map before mutating it.
Cost: maps.Clone is O(n) in the number of entries. It copies each key and value into a fresh map. The copy is shallow: if your value type is a pointer, slice, or map, both copies still share those underlying objects.
The shallowness matters. Consider a catalog whose values are slices of tags:
Both maps see the change because maps.Clone copied the slice headers, not the backing arrays. When you need a deep copy, walk the values manually and clone each one.
Calling maps.Clone on a nil map returns nil, not an empty map. Most code treats them the same, but if you're going to write to the result afterward, allocate one explicitly with make.
maps.Equal checks whether two maps have the same set of keys and the same value for each key. It uses == on the values, so the value type must be comparable.
Notice that key insertion order doesn't matter, only the (key, value) pairs do. cart1 and cart2 are equal even though they were built in different orders, because maps are unordered. cart1 and cart3 differ on the quantity of Pen, so they're not equal.
Cost: maps.Equal is O(n). It scans every entry in the first map and does an O(1) lookup in the second. It bails out early on the first mismatch, but the worst case touches every key.
For value types that don't support ==, like slices or other maps, use maps.EqualFunc. It swaps the built-in equality for a custom predicate.
The predicate runs once per key. Here slices.Equal does element-wise comparison of each tag list, which is what you want for non-comparable value types.
maps.EqualFunc also handles fuzzy equality. Comparing two pricing maps for "close enough" prices is a one-liner:
Floating-point equality with == is brittle. A tolerance-based comparison wrapped in maps.EqualFunc is the right pattern when prices come from different sources that might round differently.
maps.Copy(dst, src) copies every key-value pair from src into dst. If a key already exists in dst, the value from src overwrites it. This is the standard way to merge two maps.
Pen was overwritten from 1.50 to 0.99, and USB Cable was added. Notebook was left alone because promo didn't mention it. Map iteration order is randomized, so the printed order can vary between runs.
Cost: maps.Copy is O(n) in the size of src. It does one map insertion per source entry, so the destination may also grow its internal bucket array if it gets too full.
maps.Copy mutates dst. It returns nothing. If you want a merged result without touching the original, clone first and then copy into the clone:
base is untouched. merged reflects the promotional price.
A common question is "how do I merge maps without overwriting?". The standard library doesn't ship that variant, but it's a four-line loop:
The point here is that maps.Copy always overwrites, so reach for a manual loop when "first write wins" is what you want.
maps.DeleteFunc(m, pred) removes every entry for which pred(key, value) returns true. The map is mutated in place.
Out-of-stock items are gone. Items with positive quantity remain. The predicate takes both the key and the value, so you can filter on either or both.
Cost: maps.DeleteFunc is O(n). It evaluates the predicate once per entry. There's no early exit, since the function has to visit every key.
Conditional deletion replaces the manual pattern of collecting keys into a slice and then deleting them in a second pass:
maps.DeleteFunc does both passes internally and gives you a one-liner that's easier to read. It's also safe to use directly because the runtime guarantees that delete during a range loop on the same map is well-defined.
Go 1.23 added range-over-function and a small iter package with two core types: iter.Seq[K] (single-value sequence) and iter.Seq2[K, V] (key-value sequence). The maps package gained three functions that produce these sequences and two that consume them.
maps.Keys(m) returns an iter.Seq[K] that yields each key in the map. maps.Values(m) returns an iter.Seq[V] over the values. maps.All(m) returns an iter.Seq2[K, V] over the pairs.
maps.Keys returns an iterator, not a slice. The for ... range form on a function value is the Go 1.23 syntax that pulls each yielded value out one at a time. The keys come out in randomized order, exactly like a plain for k := range prices loop.
A common question is "how do I get the keys as a slice?". Combine maps.Keys with slices.Collect:
slices.Collect drains an iter.Seq[T] into a []T. Combined with slices.Sort, this is the idiomatic way to get a sorted slice of map keys for display.
Go 1.23 also ships slices.Sorted, which does the collect-and-sort in one call:
Cost: slices.Sorted(maps.Keys(m)) is O(n log n): linear to materialize the keys plus n log n to sort them. If you only need to walk the keys once in any order, range directly over maps.Keys and skip the slice.
maps.Values works the same way for the value side:
The first loop accumulates a running total. The second line collects the values into a slice and feeds them to slices.Min. There's no maps.Min, so this composition is how you find the smallest value across a map.
maps.All yields key-value pairs:
At first glance maps.All(prices) looks identical to plain range prices, and it is for this single use. The point of returning an iterator is that you can pass it around as a value: hand it to a function, transform it, or feed it into maps.Insert or maps.Collect.
maps.Insert(m, seq) takes a destination map and an iter.Seq2[K, V], then inserts each pair from the sequence into the map, overwriting on conflicts. It's the iterator-flavored version of maps.Copy.
For this specific case, maps.Copy(prices, promo) does the same thing with less typing. maps.Insert becomes useful when the source isn't a map: any iter.Seq2 works, including custom iterators that filter, transform, or stream from disk.
maps.Collect(seq) is the inverse: it consumes an iter.Seq2[K, V] and builds a new map.
discounted returns an iter.Seq2 that yields each original entry with a discount applied. maps.Collect drains the iterator into a fresh map. This is the pattern for "build a map by transforming another map" without writing an explicit loop and explicit make.
Cost: maps.Insert and maps.Collect are both O(n) in the number of pairs the sequence yields. They do one map write per pair, plus whatever work the iterator itself performs.
The real power of maps shows up when you compose it with slices. Here's a flow that snapshots a catalog, prunes out-of-stock items, and prints the survivors sorted by name.
Three packages, four standard-library calls, one cohesive flow. maps.Clone detaches the snapshot from the live catalog so the deletion doesn't lose data. maps.DeleteFunc prunes in place. slices.Sorted(maps.Keys(...)) produces deterministic output order. This is the idiomatic Go 1.23+ way to do "show me the live inventory, sorted".
Another composition: total revenue across the catalog.
Plain for _, v := range revenue does the same job without maps.Values. The iterator form pays off when you want to pass the value sequence to another function, chain it through slices.Collect, or compose it with a filter. For a single in-place sum, the bare range is fine.
A few patterns recur often enough to call out explicitly.
Pattern 1: deterministic iteration order. Maps iterate in randomized order, but tests and logs often need stable output. Sort the keys first.
Without the sort, the same program prints the three entries in any order on each run.
Pattern 2: snapshot before mutation. When you're about to filter, replace, or otherwise destructively transform a map but need the original later, clone first.
This is also useful for diffing: keep before, mutate cart, then compare with maps.Equal to detect changes.
Pattern 3: merge with override. Use maps.Copy (or maps.Insert with maps.All) to layer one map on top of another. The latest write wins, which is what you want for promotional pricing, environment-specific configuration, or applying user overrides on top of defaults.
Pattern 4: filter by value. Use maps.DeleteFunc rather than the manual two-pass deletion. It's shorter, safer, and clearer about intent.
Pattern 5: build a derived map. When the new map is a transformation of an existing one, write an iterator and let maps.Collect build the result. It separates the transformation logic from the map construction, which makes the transformation easy to test in isolation.
| Function | Purpose | Complexity | Notes |
|---|---|---|---|
maps.Clone(m) | Shallow copy of map | O(n) | New buckets, shared pointee values. nil in returns nil |
maps.Equal(m1, m2) | Element-wise equality | O(n) | Value type must be comparable. Bails out on first mismatch |
maps.EqualFunc(m1, m2, eq) | Equality with predicate | O(n) | For non-comparable types or fuzzy match |
maps.Copy(dst, src) | Merge src into dst, overwriting | O(n) | Mutates dst. Latest write wins |
maps.DeleteFunc(m, pred) | Remove entries where predicate is true | O(n) | Mutates m. Safe within the runtime |
maps.Keys(m) | Iterator over keys (Go 1.23+) | O(n) to drain | Returns iter.Seq[K], not a slice |
maps.Values(m) | Iterator over values (Go 1.23+) | O(n) to drain | Returns iter.Seq[V], not a slice |
maps.All(m) | Iterator over key-value pairs (Go 1.23+) | O(n) to drain | Returns iter.Seq2[K, V] |
maps.Insert(m, seq) | Insert all pairs from a Seq2 (Go 1.23+) | O(k) for k pairs | Mutates m. Overwrites on conflicts |
maps.Collect(seq) | Build map from a Seq2 (Go 1.23+) | O(k) for k pairs | Allocates a new map |
maps.Clone(m) returns a shallow copy of a map with its own storage. The copy is shallow, so slice and pointer values are still shared.maps.Equal checks key-value equality with ==. maps.EqualFunc swaps in a custom predicate for non-comparable types or fuzzy matches.maps.Copy(dst, src) merges src into dst, overwriting existing keys. Clone first if you need to keep the original.maps.DeleteFunc(m, pred) removes entries in place where the predicate returns true. It replaces the manual two-pass deletion pattern.maps.Keys (iter.Seq[K]), maps.Values (iter.Seq[V]), and maps.All (iter.Seq2[K, V]).maps.Insert(m, seq) is the iterator-flavored maps.Copy. maps.Collect(seq) builds a new map from an iter.Seq2.slices.Sorted(maps.Keys(m)).maps functions are O(n) in the number of entries they touch. There's no hidden cost beyond one pass through the map.The next part of this section is the capstone lab, which pulls together map basics, the comma-ok idiom, sets-with-maps, and the maps package into a single end-to-end inventory and cart project.