How to merge maps in Go (Golang)

Along with slice, map is one of the most useful data structures provided in the Go (Golang) standard library. An implementation of HashMap, maps in Go (Golang) allow you to store and access key-value pairs in constant time. Merging two maps in Go is a common use case and there are multiple possible approaches depending on your requirements.

Merge maps in Go (Golang) using for loop

The simplest approach to merge two maps in Go (Golang) is to write custom code to iterate through the key-value pairs of each map and add them to a new map which contains the merged result:

package main

import "fmt"

func main() {
	m1 := map[string]int{
		"alice": 1,
		"jane":  2,
	}

	m2 := map[string]int{
		"bob":   3,
		"fred":  4,
		"alice": 1,
	}

	mergedMap := mergeMaps(m1, m2)

	fmt.Printf("Merged maps: %v", mergedMap)
}

func mergeMaps(m1 map[string]int, m2 map[string]int) map[string]int {
	merged := make(map[string]int)
	for k, v := range m1 {
		merged[k] = v
	}
	for key, value := range m2 {
		merged[key] = value
	}
	return merged
}

Running this code outputs the below text:

Merged maps: map[alice:1 bob:3 fred:4 jane:2]

In the above code, a new map is created to store the merged result. This means the original maps are unchanged, but at the cost of the additional memory used by the new map. A small amount of memory could be saved by rewriting the mergeMaps function to "reuse" one of existing maps:

package main

import "fmt"

func main() {
	m1 := map[string]int{
		"alice": 1,
		"jane":  2,
	}

	m2 := map[string]int{
		"bob":   3,
		"fred":  4,
		"alice": 1,
	}

	mergeMaps(m1, m2)

	fmt.Printf("Merged maps: %v", m1)
}

func mergeMaps(m1 map[string]int, m2 map[string]int) {
	for k, v := range m2 {
		m1[k] = v
	}
}

Now, rather than create a new map, all existing key-value pairs from m2 are added to m1.

The benefit of using this Custom Code to merge maps is that it provides you with complete control over how you want the merge process to work. For example, in the above code, when the same key exists in both maps the value from m2 is always used. However, let’s imagine the maps are being used to count student attendance across different classes and the requirements are that the merged map should store the total student attendance. With the custom code approach, this is a simple change to implement:

package main

import "fmt"

func main() {
	m1 := map[string]int{
		"alice": 2,
		"jane":  2,
	}

	m2 := map[string]int{
		"alice": 2,
		"jane":  3,
		"bob":   4,
	}

	mergedMap := mergeMaps(m1, m2)

	fmt.Printf("Merged maps: %v", mergedMap)
}

func mergeMaps(m1 map[string]int, m2 map[string]int) map[string]int {
	merged := make(map[string]int)
	for k, v := range m1 {
		merged[k] = v
	}

	for key, value := range m2 {
		//Rather than replacing the existing value for the student,
		//add on to any value we already stored.
		merged[key] = merged[key] + value
	}
	return merged
}

Running this code produces the following output:

Merged maps: map[alice:4 bob:4 jane:5]

The merged map now contains the summed values of each key.

Note that this example also makes use of another feature of Go maps whereby accessing a non-existent key returns the zero value for the map's value type - in this case, 0. You can read more about this feature on the official Go blog, see "Exploiting zero values"

The downside of the custom code approach is that if you need to merge maps of many data types you may find yourself writing a lot of similar merge functions. For example, to merge maps of type map[string]bool, you would end up writing an almost identical function:

package main

import "fmt"

func main() {
	m1 := map[string]bool{
		"alice": true,
		"jane":  true,
		"fred":  true,
	}

	m2 := map[string]bool{
		"alice": true,
		"jane":  true,
		"bob":   true,
	}

	mergedMap := mergeMaps(m1, m2)

	fmt.Printf("Merged maps: %v", mergedMap)
}

func mergeMaps(m1 map[string]bool, m2 map[string]bool) map[string]bool {
	merged := make(map[string]bool)
	for k, v := range m1 {
		merged[k] = v
	}
	for k, v := range m2 {
		merged[k] = v
	}
	return merged
}

The only difference here from the map[string]int version is the type definitions. One solution to this problem is to use generics.

Merge maps in Go (Golang) using Generics

Generics were officially added to Go in v1.18 and enable you to write code that is compatible with a set of predefined types rather than being limited to specific types. Generics can be used to write a new version of the mergeMaps function that will work with both map[string]int and map[string]bool:

package main

import "fmt"

func main() {
	m1 := map[string]int{
		"alice": 2,
		"jane":  2,
	}

	m2 := map[string]int{
		"alice": 2,
		"jane":  3,
		"bob":   4,
	}

	mergedIntMap := mergeMaps(m1, m2)

	fmt.Printf("Merged int maps: %v", mergedIntMap)

	m3 := map[string]bool{
		"alice": true,
		"jane":  true,
		"fred":  true,
	}

	m4 := map[string]bool{
		"alice": true,
		"jane":  true,
		"bob":   true,
	}

	mergedBoolMap := mergeMaps(m3, m4)

	fmt.Printf("Merged bool maps: %v", mergedBoolMap)
}

func mergeMaps[K comparable, V any](m1 map[K]V, m2 map[K]V) map[K]V {
	merged := make(map[K]V)
	for key, value := range m1 {
		merged[key] = value
	}
	for key, value := range m2 {
		merged[key] = value
	}
	return merged
}

The downside of using generics in this way is that it’s now more difficult to write type-specific merge logic as in the previous example. To support the previous merge logic that summed the values for existing keys a specific version of the mergeMaps function for numeric types would still need to be defined. To work with maps of non-numeric types, additional type-specific versions of the function would need to be created. If you end up with many of these, you are essentially back to where you started with Option 1. For this reason, the generics approach is useful when you know you are unlikely to have type-specific custom merge logic.

For a further explanation of generics, see the official Go Getting Started with Generics Tutorial.

Merge maps in Go (Golang) using Copy Method

Finally, if you want to avoid writing any custom code at all, you can use the Copy method from the /x/exp/maps package.

The package is a member of the x package family meaning it belongs to the Go project but is maintained in a separate repository. It must therefore be installed using go get

package main

import (
	"fmt"
	"golang.org/x/exp/maps"
)

func main() {
	m1 := map[string]int{
		"alice": 2,
		"jane":  2,
	}

	m2 := map[string]int{
		"alice": 2,
		"jane":  3,
		"bob":   4,
	}

	maps.Copy(m1, m2)

	fmt.Printf("Merged int maps: %v", m1)
}

The main downside of the Copy method is that it’s behaviour cannot be customised. Copy will add all key-value pairs from the second map to the first map - it’s not possible to have Copy return a new map. Also, in the case where the maps contain duplicate keys, Copy will always take the value from the second map.

In addition to these drawbacks, the Copy method is part of an experimental package (note the exp in the package name). This means that in the future the Copy method may be changed or removed entirely. For more details, see the exp package documentation

Copy can be an easy, concise way to quickly merge maps when you don’t have a need for custom merge logic but should be used with caution in production code due to its experimental nature.

Join the Golang Developers Community on Golang Cafe