Go Maps
Go Maps
A map is Go's built-in key–value data structure. Each key is unique and points to exactly one value, which makes maps the natural choice whenever you need fast lookups by some identifier — a username, a product code, a country abbreviation — rather than by numeric position.
Compared to arrays and slices, maps are:
- Unordered — iterating over a map gives no guaranteed order, and that order can even differ between runs of the same program.
- Mutable — you can add, update, and delete entries freely.
- Reference types — a map variable is a reference to an underlying hash table, not the data itself (this has real consequences, covered below).
Key Features of Maps
- Store data as
key: valuepairs. - Keys must be unique — assigning to an existing key overwrites its value rather than creating a duplicate.
len(m)returns the number of key–value pairs.- The zero value of a map is
nil, not an empty, usable map — this distinction matters and is explained below. - Internally implemented as a hash table, giving average constant-time lookups, inserts, and deletes.
Creating Maps
1. Map Literals
package main
import "fmt"
func main() {
countryCodes := map[string]string{
"IN": "India",
"US": "United States",
"JP": "Japan",
}
scores := map[string]int{
"Alice": 90,
"Bob": 85,
"Eve": 88,
}
fmt.Println("Countries:", countryCodes)
fmt.Println("Scores:", scores)
}
Note: because maps are unordered, running this program multiple times can print the entries in a different order each time — this is expected behavior, not a bug.
2. Using make()
package main
import "fmt"
func main() {
inventory := make(map[string]int)
inventory["Apples"] = 50
inventory["Bananas"] = 30
inventory["Oranges"] = 20
fmt.Println("Inventory:", inventory)
}
nil Maps vs. Initialized Maps
This is one of the most important distinctions to understand about Go maps, and a frequent source of runtime panics for beginners:
var m1 = make(map[string]int) // initialized — ready to use
var m2 map[string]int // nil map — declared, but not initialized
- Writing to
m1works normally. - Writing to
m2causes a runtime panic —assignment to entry in nil map— because there is no underlying hash table for it to write into yet. - Reading from a
nilmap, by contrast, is perfectly safe:m2["anything"]simply returns the zero value for the map's value type (in this case0), with no panic.
Rule of thumb: always initialize a map with make() or a map literal before writing to it. A var-declared map with no initializer is only safe to read from, never to write to.
Allowed Key and Value Types
Valid key types — any type that supports the == operator:
- Numbers (
int,float64, etc.) - Strings
- Booleans
- Arrays
- Structs (as long as every field is itself comparable)
- Pointers
- Interfaces, if the underlying value is comparable
Invalid key types — these cannot be compared with ==, and Go refuses to compile a map using them as keys:
- Slices
- Maps
- Functions
Value types can be absolutely anything, including another map or a slice.
Accessing, Adding, and Updating Elements
price := map[string]int{
"Pen": 10,
"Book": 50,
}
fmt.Println(price["Pen"]) // 10
cart := make(map[string]int)
cart["Milk"] = 2 // Add
cart["Bread"] = 1 // Add
cart["Milk"] = 3 // Update — overwrites the previous value for "Milk"
fmt.Println(cart)
Deleting Elements
delete(cart, "Bread")
delete() on a key that doesn't exist is safe and does nothing — it will not panic, unlike writing to a nil map.
Checking Whether a Key Exists
Indexing a map always returns two values when you ask for both: the value itself, and a bool indicating whether the key was actually present.
studentMarks := map[string]int{
"Rahul": 80,
"Neha": 90,
}
marks, exists := studentMarks["Rahul"]
fmt.Println(marks, exists) // 80 true
_, found := studentMarks["Amit"]
fmt.Println(found) // false
This two-value form matters because a missing key and a key whose value happens to be the zero value look identical if you only check the value: studentMarks["Amit"] alone would also return 0, indistinguishable from a student who legitimately scored 0. Checking the second return value is the only reliable way to tell "key absent" apart from "key present with a zero value."
Maps Are Reference Types
Unlike a struct or an array, a map variable doesn't hold its data directly — it holds a reference to an underlying hash table. Assigning one map variable to another does not copy the data; both variables end up pointing at the same table.
package main
import "fmt"
func main() {
original := map[string]int{"A": 1, "B": 2}
copyMap := original
copyMap["A"] = 100
fmt.Println("Original:", original) // also shows A: 100
fmt.Println("Copy:", copyMap)
}
Expected output:
Original: map[A:100 B:2]
Copy: map[A:100 B:2]
Both variables reflect the same underlying change, because they reference the same map. This is also why passing a map to a function lets that function modify the caller's data directly — no pointer is needed, unlike with a struct.
To make an independent copy, you must explicitly create a new map and copy each entry over (Go added a built-in maps.Copy helper in the maps package starting in Go 1.21, or you can loop over the source map with range and assign each key into a fresh map yourself).
Iterating Over a Map
package main
import "fmt"
func main() {
items := map[string]int{
"Pen": 10,
"Pencil": 5,
"Eraser": 3,
}
for key, value := range items {
fmt.Println(key, ":", value)
}
}
The iteration order is not guaranteed — Go intentionally randomizes map iteration order between runs to prevent code from accidentally relying on an order that was never promised.
Iterating in a Specific Order
To guarantee a consistent order, extract the keys into a slice, sort or arrange that slice as needed, and iterate through the slice instead:
package main
import "fmt"
func main() {
data := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
keys := []string{"one", "two", "three"}
for _, k := range keys {
fmt.Println(k, ":", data[k])
}
}
For keys whose order isn't known ahead of time, combine this with the sort package — for example, sort.Strings(keys) after collecting the keys with a loop — to iterate alphabetically. This pattern (collect keys, sort them, then range over the sorted slice) is the standard way to get deterministic output from map data in Go.