Passing Maps in Go: By Value or By Reference?

Dec 07, 2025 · Programming · 8 views · 7.8

Keywords: Go | map passing | reference types

Abstract: This article explores the passing mechanism of map types in Go, explaining why maps are reference types rather than value types. By analyzing the internal implementation of maps as pointers to runtime.hmap, it demonstrates that pointers are unnecessary for avoiding data copying in function parameters and return values. Drawing on official documentation and community discussions, the article clarifies the design background of map syntax and provides practical code examples to help developers correctly understand and use maps, preventing unnecessary performance overhead and syntactic confusion.

Introduction

In Go, maps are a commonly used data structure for storing key-value pairs. However, many developers are confused about their passing mechanism: are maps passed by value or by reference when used as function parameters or return values? Understanding this is crucial for writing efficient and correct code. This article delves into the design principles of Go, combining official documentation and community discussions to thoroughly analyze the passing mechanism of maps.

Reference Type Nature of Maps

According to the official Go documentation, map types are classified as reference types, similar to pointers and slices. This means that a map variable does not directly store data but contains a pointer to an underlying data structure. Therefore, when a map is passed as a function parameter, a copy of this pointer is passed, not a copy of the entire data structure. This avoids unnecessary data copying and improves performance.

For example, consider the following code snippet:

func modifyMap(m map[string]int) {
    m["key"] = 42
}

func main() {
    myMap := make(map[string]int)
    myMap["key"] = 0
    modifyMap(myMap)
    fmt.Println(myMap["key"]) // Output: 42
}

In this example, the modifyMap function receives a map parameter and modifies its content. Since maps are reference types, the changes inside the function are reflected in the original map, outputting 42. This demonstrates that maps are passed in a manner similar to by reference, eliminating the need for pointers to avoid data copying.

Internal Implementation of Maps

To gain a deeper understanding of map passing, we must examine their internal implementation. Based on community discussions and source code analysis, a map value is essentially a pointer to a runtime.hmap structure. This structure is defined in the Go runtime and manages the underlying data storage and operations of the map.

Dave Cheney notes in his article that types like maps and channels are pointers at the low level, but syntactically designed without explicit * symbols. This design simplifies coding, as developers almost always use maps without needing pointer dereferencing. In early Go versions, map types were written as *map[key]value, but later changed to map[key]value to eliminate redundancy and reduce confusion.

For instance, make(map[int]int) returns a value of type map[int]int, not *map[int]int. Although this does not look like a pointer, its behavior is consistent with pointers, and the compiler rewrites map operations into runtime function calls.

Practical Advice for Function Parameters and Return Values

Based on the reference type nature of maps, pointers are generally unnecessary for function parameters and return values. Using pointers may introduce unnecessary complexity and performance overhead. Here are some practical recommendations:

Consider the following code example, illustrating correct usage of maps as function parameters and return values:

func createMap() map[string]int {
    m := make(map[string]int)
    m["a"] = 1
    m["b"] = 2
    return m // Return value directly, no pointer needed
}

func updateMap(m map[string]int) {
    m["c"] = 3
}

func main() {
    myMap := createMap()
    updateMap(myMap)
    fmt.Println(myMap) // Output: map[a:1 b:2 c:3]
}

In this example, the createMap function returns a map value, and the updateMap function receives a map parameter and modifies it. Since maps are reference types, all operations are efficient and do not require pointers.

Comparison with Slices and Pointers

To better understand map passing, it can be compared with slices and ordinary pointers. Slices are also reference types, but their underlying structure includes a pointer to an array, length, and capacity. When a slice is passed as a function parameter, a copy of this structure is passed, but the pointer points to the same array, so modifying elements affects the original slice. However, if you modify the slice's length or capacity (e.g., using append), you might need to return a new slice or use a pointer.

In contrast, map passing is simpler because its behavior is entirely controlled by pointers, without additional metadata like slices. Ordinary pointers require explicit * and & operators, while maps hide these details through syntactic sugar.

For example, for cases requiring modification of Session objects, you can use map[string]*Session, where the value part is a pointer, but this is unrelated to the passing mechanism of the map itself.

Conclusion

In summary, maps in Go are reference types, with values essentially being pointers to runtime.hmap structures. When passed as function parameters or return values, maps pass pointer copies by value, avoiding data copying, so pointer types are unnecessary. This design simplifies code, enhances performance, and reduces syntactic redundancy. Developers should use map types directly instead of pointers, unless specific needs arise (e.g., storing pointer values). By understanding the internal implementation and passing mechanism of maps, you can write more efficient and clear Go code.

This article, based on official documentation and community discussions, clarifies common misconceptions about map passing. In practice, following these best practices will help avoid unnecessary performance overhead and code confusion. For further learning, refer to the Go official blog and runtime source code to deepen your understanding of map and other data structure implementations.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.