How to Clone a Map in Go Without Sharing the Underlying Values
Clone Go maps safely with maps.Clone, maps.Copy, or a manual deep copy that prevents slices, maps, and pointers from being shared.

The short answer: use maps.Clone when you need a new map and ordinary assignment is sufficient for its values. If the values contain slices, maps, or pointers that must not be shared, allocate and copy those values yourself.
Assigning a map to another variable is not a clone at all.
Why assigning a map does not copy it
Maps are reference types. An assignment such as b := a gives you another name for the same map data, so writes through either variable are visible through the other.
package main
import "fmt"
func main() {
a := map[string]int{"jobs": 1}
b := a
b["jobs"] = 2
fmt.Println(a["jobs"]) // 2
fmt.Println(b["jobs"]) // 2
}
No entries were copied here. This is useful when sharing is intentional, but it is an easy source of bugs when a function is expected to modify a private copy.
A shallow copy with maps.Clone
For Go 1.21 and later, the standard-library maps package provides Clone. Its generic signature is:
func Clone[M ~map[K]V, K comparable, V any](m M) M
Clone creates a new map and sets its keys and values using ordinary assignment. With values such as integers, replacing an entry in the clone does not replace the corresponding entry in the original:
package main
import (
"fmt"
"maps"
)
func main() {
original := map[string]int{
"retries": 3,
}
cloned := maps.Clone(original)
cloned["retries"] = 10
fmt.Println(original["retries"]) // 3
fmt.Println(cloned["retries"]) // 10
}
The two variables now refer to different maps. Adding, deleting, or replacing an entry in cloned therefore does not make the same structural change to original.
The trap: Clone does not copy referenced data
maps.Clone is explicitly a shallow clone. If a value is a slice, map, or pointer, ordinary assignment copies that reference-like value—not the data it leads to.
The standard documentation illustrates the issue with a map[string][]int:
package main
import (
"fmt"
"maps"
)
func main() {
m3 := map[string][]int{
"key": []int{1, 2, 3},
}
m4 := maps.Clone(m3)
fmt.Println(m4["key"][0]) // 1
m4["key"][0] = 100
fmt.Println(m3["key"][0]) // 100
fmt.Println(m4["key"][0]) // 100
}
m3 and m4 are separate maps, but their "key" entries contain slices backed by the same data. Replacing the entire entry in m4 would leave m3 alone; modifying an element through the shared slice does not.
The rule is straightforward: maps.Clone copies the map, not what its values point to.
Deep-copying a map whose values are slices
When referenced values must be independent, allocate a new map and copy each value according to its type. For slices, that means allocating a new slice and copying its elements:
package main
import "fmt"
func cloneStringIntSlices(src map[string][]int) map[string][]int {
dst := make(map[string][]int, len(src))
for key, values := range src {
if values == nil {
dst[key] = nil
continue
}
valuesCopy := make([]int, len(values))
for i, value := range values {
valuesCopy[i] = value
}
dst[key] = valuesCopy
}
return dst
}
func main() {
original := map[string][]int{
"ports": []int{8080, 8081, 8082},
}
cloned := cloneStringIntSlices(original)
cloned["ports"][0] = 9090
fmt.Println(original["ports"]) // [8080 8081 8082]
fmt.Println(cloned["ports"]) // [9090 8081 8082]
}
This copies both levels: the map and every slice stored in it. The explicit nil check also preserves nil slice values rather than turning them into allocated empty slices.
There is no universal deep-copy operation because the correct policy depends on the value type. A struct may contain slices or pointers several levels down. A pointer may represent deliberately shared state. Nested maps may require recursion. Each additional allocation also costs time and memory, so copy only the levels that must be independent.
Before the standard-library maps package was added in Go 1.21, allocating a map and ranging over the source was the standard-library copying pattern. It remains the appropriate pattern when you need value-specific deep-copy behavior.
Merging with maps.Copy
Use maps.Copy when the destination map already exists and you want to merge another map into it. Its signature is:
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)
Every pair from src is added to dst. If both maps contain the same key, the source value overwrites the destination value.
package main
import (
"fmt"
"maps"
)
func main() {
dst := map[string]int{
"workers": 2,
"timeout": 30,
}
src := map[string]int{
"workers": 8,
"retries": 3,
}
maps.Copy(dst, src)
fmt.Println(dst["workers"]) // 8: overwritten
fmt.Println(dst["timeout"]) // 30: retained
fmt.Println(dst["retries"]) // 3: added
}
Like Clone, Copy uses ordinary value assignment and is shallow. Copying slice-valued entries into dst does not duplicate their underlying elements.
Choosing the right operation
Use the operation that matches the ownership you need:
- Use
maps.Clonefor a fresh map when its values can safely be assigned as-is. - Use
maps.Copyto add or overwrite entries in an existing map. - Use a hand-written loop when slices, nested maps, pointers, or other referenced data must not be shared.
- Use plain assignment only when both variables are intentionally meant to access the same map.
One final edge case: the maps package performs no special handling for non-reflexive keys—keys for which k != k—such as floating-point NaNs. If a map uses such keys, cloning and other package operations do not compensate for their unusual equality behavior.





Comments
No comments yet.