Reading and Writing Map Entries
How to assign values to map keys, retrieve stored data, and understand what happens when a key is missing
The Indexing Syntax
Reading from a map and writing to a map both use the same bracket notation you’d expect from other languages. A map value followed by square brackets containing a key is an expression you can use on the right-hand side of an assignment to read, or on the left-hand side to write.
scores := map[string]int{
"Alice": 92,
"Bob": 87,
}
// Reading: assign the value stored under "Alice" to a variable
aliceScore := scores["Alice"]
// Writing: set or update the value stored under "Charlie"
scores["Charlie"] = 95
The expression scores[key] works for any expression of the map’s key type. If the key exists, a read gives you the value that was previously stored there. A write either creates a new entry or replaces the existing one — you don’t need to check whether the key exists before writing.
The same syntax for both:
Reading and writing share the same bracket notation. There’s no separate get or put function — the built-in syntax handles both cases, which keeps map code concise.
What Happens When You Read a Missing Key
When you read a key that hasn’t been written to the map, Go does not throw an error or return an optional value. It returns the zero value for the map’s value type.
The zero value is the default value a variable of that type holds when it is declared but not explicitly initialized. For common types:
| Value type | Zero value returned for a missing key |
|---|---|
int | 0 |
float64 | 0.0 |
string | "" (empty string) |
bool | false |
| pointers, slices, maps, channels, functions | nil |
counters := map[string]int{"apples": 5}
oranges := counters["oranges"] // 0 — the key "oranges" does not exist
fmt.Println(oranges) // prints 0
flags := map[string]bool{"verbose": true}
quiet := flags["quiet"] // false — key absent
fmt.Println(quiet) // prints false
This design means a single-value read always succeeds, which simplifies a lot of map code. But it creates a problem: you can’t tell the difference between a key that was never set and a key that was intentionally set to the zero value.
Zero-value ambiguity can hide bugs:
A map of map[string]int used for event counters is a classic trap. A key missing entirely and a key set to 0 both give you 0 on read. If zero is a meaningful value for your domain, you must check existence separately. The two-value assignment (the comma-ok idiom) solves this.
Beginners can think of it this way: reading a map with a single value answers “what number is on this slip of paper?” — if there is no slip, you see a blank (zero). To first ask “does a slip exist?” you need a different form of read.
Writing to a Map
Assigning a value to a map key uses the same bracket expression on the left side of an =.
statuses := map[string]string{
"build": "passing",
}
// Add a new entry
statuses["deploy"] = "in_progress"
// Update an existing entry
statuses["build"] = "failing"
fmt.Println(statuses["build"]) // "failing"
fmt.Println(statuses["deploy"]) // "in_progress"
There is no separate insert versus update operation. If the key is not in the map, the assignment adds it. If it already exists, the new value replaces the old one. This is identical to how slices and arrays use index assignment, but keys are arbitrary comparable values, not sequential integers.
The Nil Map Trap
A map variable declared without initialization has the value nil. It contains no hash table at all.
var cache map[string]string // nil map
Reading from a nil map behaves like reading from an empty map: you get the value type’s zero value. Writing to a nil map, however, causes a runtime panic.
var cache map[string]string
cache["user:42"] = "data" // panic: assignment to entry in nil map
This is the most common map-related crash for newcomers — they declare a map variable but forget to call make or initialize it with a literal. The program compiles fine and only fails at runtime when the write path first executes.
Writing to a nil map panics:
Always initialize a map before writing to it. Use make(map[K]V), a map literal map[K]V{}, or ensure a function has already populated the variable. A nil map is fine for reads only.
Reading from a Nil Map
Reading from a nil map is safe and returns the zero value for the value type. This can be deliberate in patterns where a map is lazily initialized — the read path works correctly even before the first write occurs.
var items map[int]string // nil
val := items[42] // val is "" — no panic
fmt.Println(val) // prints empty string
That said, relying on a nil map for reads without eventually initializing it is fragile. Once any code path tries to write to it, your program will panic. A better habit is to initialize maps at the point of declaration.
Nil map reads are allowed but not a substitute for initialization:
While nil map reads are safe, leaving a map permanently nil is rarely intentional. Initialize maps with make or {} unless you have a specific reason to delay allocation.
The Missing-Key Problem in Real Code
The zero-value default is convenient but can hide logical errors when zero is a valid piece of data. Consider a map that tracks which features are enabled:
features := map[string]bool{
"darkMode": true,
"newDashboard": false,
}
// Later in the code...
if features["betaSearch"] {
// This block never runs because the key is missing → false
}
Here "betaSearch" doesn’t exist, so the lookup returns false. But "newDashboard" also returns false because it was explicitly set that way. The if statement treats both identically, even though one is “explicitly off” and the other is “never configured.” The meaning is lost.
For cases like this, Go provides the two-value assignment form, often called the comma-ok idiom:
enabled, exists := features["betaSearch"]
// enabled == false, exists == false
The second value is a boolean that reports whether the key was present in the map. This is the fundamental tool for solving zero-value ambiguity.
When you need distinction, the comma-ok idiom is the answer:
If your map stores a type whose zero value overlaps with meaningful data — 0, false, "" — always use the two-value assignment to check existence before acting on the retrieved value.
Summary
Reading and writing map entries in Go is syntactically uniform: the bracket notation m[key] works as both a getter and a setter. A read of a missing key returns the value type’s zero value, which simplifies many common patterns but creates an ambiguity when zero has business meaning. Writing adds or updates an entry immediately and requires the map to be initialized — writing to a nil map is a runtime panic. The comma-ok idiom resolves the zero-value ambiguity entirely and gives you a reliable way to test for key presence.