Represent and transform data safely
How to Merge Two Maps in Go
Learn how to merge two maps in Go, handle duplicate keys, preserve inputs, use maps.Copy and generics, and decide when mergo fits.

The idiomatic way to merge two Go maps is straightforward: range over the source map and assign each value to the destination.
for k, v := range src {
dst[k] = v
}
If both maps contain the same key, the value from src replaces the value already in dst. The main design decision is whether dst should be one of your existing maps or a newly allocated map.
Merge in place or create a new map?
An in-place merge modifies an existing destination map:
package main
import "fmt"
func main() {
dst := map[string]int{
"one": 1,
"two": 2,
}
src := map[string]int{
"three": 3,
}
for k, v := range src {
dst[k] = v
}
fmt.Println(dst)
}
This avoids allocating a third map, but callers that retain dst will observe the changes. It is a good fit when the destination is explicitly being built or updated.
If neither input should change, allocate a third map and copy both inputs into it:
package main
import "fmt"
func main() {
left := map[string]int{
"apples": 2,
"shared": 10,
}
right := map[string]int{
"bananas": 3,
"shared": 20,
}
merged := make(map[string]int)
for k, v := range left {
merged[k] = v
}
for k, v := range right {
merged[k] = v
}
fmt.Println("left shared:", left["shared"])
fmt.Println("right shared:", right["shared"])
fmt.Println("merged shared:", merged["shared"])
}
The values printed for shared are 10, 20, and 20. The original maps remain unchanged, while the second copy loop determines the value in the result. This shape costs additional memory for the third map, in exchange for preserving both inputs.
Duplicate keys: the source wins
Map assignment does not distinguish between inserting a new key and replacing an existing one. Consequently, this merge overwrites collisions:
package main
import "fmt"
func main() {
dst := map[string]int{
"one": 1,
"two": 2,
}
src := map[string]int{
"two": 22,
"three": 3,
}
for k, v := range src {
dst[k] = v
}
fmt.Println(dst)
}
The resulting map contains one: 1, two: 22, and three: 3. For the duplicate key two, the source value 22 wins.
The loop is also the right place to implement a different collision rule. For example, replacing the assignment with dst[k] = dst[k] + v adds values instead of overwriting them. That works because reading a missing map key returns the value type's zero value. The explicit loop is not merely a fallback; it gives you direct control over policy.
Use maps.Copy when overwrite semantics are enough
For Go 1.21 and later, the standard library's maps.Copy expresses the same source-wins operation more concisely:
package main
import (
"fmt"
"maps"
)
func main() {
dst := map[string]int{
"one": 1,
"two": 2,
}
src := map[string]int{
"two": 22,
"three": 3,
}
maps.Copy(dst, src)
fmt.Println(dst)
}
maps.Copy copies every entry from src into dst, replacing destination values for duplicate keys. Semantically, it performs the same operation as the manual assignment loop. According to this overview of map-merging options, projects on Go 1.18 through 1.20 can use the similar Copy function from golang.org/x/exp/maps.
Choose maps.Copy when ordinary overwrite behavior says exactly what you mean. Keep the loop when collisions require addition, filtering, validation, or another application-specific rule.
Write one generic merge helper
Generics let one helper work with different key and value types. This version creates a new result, so it does not modify either input:
package main
import "fmt"
func mergeMaps[K comparable, V any](left, right map[K]V) map[K]V {
merged := make(map[K]V)
for k, v := range left {
merged[k] = v
}
for k, v := range right {
merged[k] = v
}
return merged
}
func main() {
counts := mergeMaps(
map[string]int{"requests": 10},
map[string]int{"errors": 2},
)
features := mergeMaps(
map[string]bool{"search": true},
map[string]bool{"exports": false},
)
fmt.Println(counts)
fmt.Println(features)
}
The comparable constraint is used for keys, while any allows values such as int, bool, or structs. As with the earlier new-map example, entries from right win on duplicate keys. Generics have supported this kind of reusable helper since Go 1.18, as described in this generic map merge example.
Nested values require a clearer policy
The simple loop and maps.Copy assign one value per key. If a value is itself a map, assigning it replaces the destination's value for that key rather than recursively combining the two nested maps. A struct value is likewise assigned as one value.
That may be exactly right. Configuration and defaulting code, however, often needs a different rule: retain destination fields that are already populated and fill only their zero-valued fields from a source.
The mergo package implements that kind of zero-value filling for same-type structs and maps. Its documented struct example behaves like this:
package main
import (
"fmt"
"log"
"github.com/imdario/mergo"
)
type Foo struct {
A string
B int64
}
func main() {
src := Foo{
A: "one",
B: 2,
}
dest := Foo{
A: "two",
}
if err := mergo.Merge(&dest, src); err != nil {
log.Fatal(err)
}
fmt.Println(dest) // {two 2}
}
dest.A remains "two" because it is already non-zero, while the zero-valued dest.B is filled with 2. This is intentionally different from plain map overwrite semantics.
Mergo recursively processes exported fields but does not merge unexported fields. It also does not merge structs stored inside maps because those values are not addressable through Go reflection. The package provides overwrite variants and transformers for cases requiring different per-type behavior, but those options should be chosen deliberately: default filling and unconditional source-wins assignment solve different problems.
For ordinary flat maps, prefer a loop or maps.Copy. Allocate a new map when preserving both inputs matters; merge in place when mutation is intended and the extra map would serve no purpose. Reach for a library when the requirement is genuinely structural—such as recursively filling zero-valued fields or applying custom rules to specific types—not merely copying keys.





Comments
No comments yet.