Maps
A map is a built-in data structure that stores an unordered collection of key-value pairs. Each key must be unique within a map, and it maps to exactly one value. This structure makes it efficient to look up, add, or delete values by their key.
Under the hood, Go's maps are implemented as hash maps — a data structure that uses a hash function to map keys to storage slots, enabling average O(1) lookups regardless of the number of entries.
The general syntax for a map type is map[KeyType]ValueType. For example, a map from string keys to int values:
var scores map[string]int
The len built-in returns the number of key-value pairs currently in a map:
m := map[string]int{"alice": 10, "bob": 20}
fmt.Println(len(m)) // 2
Nil map
The zero value of a map is nil. A nil map behaves like an empty map when reading, but panics on any write operation.
var m map[string]int // nil map
fmt.Println(m == nil) // true
fmt.Println(len(m)) // 0
fmt.Println(m["alice"]) // 0 — returns zero value, no panic
Reading from a nil map is safe and always returns the zero value for the value type. However, writing to a nil map causes a runtime panic:
var m map[string]int
m["alice"] = 10 // panic: assignment to entry in nil map
This is one of the most common map-related bugs in Go. Always initialize a map before writing to it.
Empty map
An empty map is initialized and ready to use — unlike a nil map, you can read from and write to it freely. The len of an empty map is also 0, just like a nil map, but it is not nil.
Go provides a few ways to create an empty map.
Map literal:
m := map[string]int{}
fmt.Println(m == nil) // false
fmt.Println(len(m)) // 0
Using make:
m := make(map[string]int)
Using make with a capacity hint:
m := make(map[string]int, 100)
The capacity hint is optional. It tells Go to pre-allocate enough internal space for roughly that many entries, which can avoid repeated internal reallocations as the map grows. It does not cap the map's size — the map still grows automatically beyond that hint.
Reading from a map
Values are read by providing the key in bracket notation:
m := map[string]int{"alice": 10, "bob": 20}
fmt.Println(m["alice"]) // 10
fmt.Println(m["carol"]) // 0 — key does not exist, returns zero value
When a key does not exist, Go silently returns the zero value for the value type. This means you cannot tell by the value alone whether a key is present or simply holds the zero value.
The comma-ok idiom
To distinguish between a missing key and a key that genuinely holds the zero value, use the comma-ok idiom:
value, ok := m["alice"]
if ok {
fmt.Println("found:", value)
} else {
fmt.Println("key not found")
}
The second return value ok is a boolean: true if the key exists in the map, false otherwise. This pattern is idiomatic Go and appears frequently whenever working with maps.
Writing to a map
Values are written using the same bracket notation:
m := make(map[string]int)
m["alice"] = 10
m["bob"] = 20
fmt.Println(m) // map[alice:10 bob:20]
Unique keys
Maps can only hold unique keys. Assigning a value to an existing key overwrites the previous value — there is no error or warning:
m := map[string]int{"alice": 10}
m["alice"] = 99
fmt.Println(m["alice"]) // 99
Comparable key types
Not all types can be used as map keys. Go requires that key types be comparable — meaning two values of that type can be tested for equality with == and !=.
Valid key types include:
- Booleans, integers, floats, and complex numbers
- Strings
- Pointers
- Arrays (not slices) of comparable element types
- Structs where all fields are comparable
The following types cannot be used as map keys:
- Slices
- Maps
- Functions
Attempting to use a non-comparable type as a key is a compile error:
m := map[[]int]string{} // compile error: invalid map key type []int