Go DISCUSSION

Go map returns 0 for a missing key: how do I tell a missing key from a stored zero value?

Started by subrat Go mapscomma ok idiomzero valuenil map panickey existence
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

Go map returns 0 for a missing key: how do I tell a missing key from a stored zero value?

subrat Go Forum
#1

I keep the last error count per device in counts := map[string]int{}. Looking up a device that was never added, counts["pump-7"], returns 0 with no error, which is indistinguishable from a device that really has zero errors. In another place I declared var limits map[string]int, and the first assignment limits["pump-7"] = 5 crashed with "panic: assignment to entry in nil map", although reading from it had worked.

How do I test whether a key exists, and why can I read from the second map but not write to it?

Community replies 5

Re: Go map returns 0 for a missing key: how do I tell a missing key from a stored zero value?

#2

Indexing a map with a missing key returns the zero value of the value type: 0, an empty string, false or nil. There is no exception. To tell the cases apart, use the two-value form: n, ok := counts["pump-7"]. ok is true when the key is present, even if its value is zero.

The usual idiom scopes both variables to the if: if n, ok := counts[id]; ok { report(n) } else { register(id) }. When only presence matters, write _, ok := counts[id]. It is a single lookup, so there is no need to test first and index again.

Re: Go map returns 0 for a missing key: how do I tell a missing key from a stored zero value?

#3

The zero-value behaviour is deliberately useful. Counting works on a new key: counts[id]++ reads 0 and stores 1. Grouping works too: groups[k] = append(groups[k], v), because a missing key yields a nil slice and append accepts nil. A set is seen := map[string]bool{} tested with if seen[id].

So the rule is: plain indexing when the zero value is a fine default, the comma-ok form when "absent" and "zero" mean different things, as in your error counts. delete(counts, id) removes a key and does nothing if the key is not there.

Re: Go map returns 0 for a missing key: how do I tell a missing key from a stored zero value?

#4

On the nil map: a map variable refers to a hash table created by the runtime. var limits map[string]int declares the variable but creates no table, so its value is nil. Reads from a nil map are defined to behave like reads from an empty one: lookups return zero values, len is 0 and range does nothing. A write has nowhere to store the entry, so it panics.

Initialise before writing, with limits := make(map[string]int) or the literal map[string]int{}; make(map[string]int, 1000) reserves room for about that many entries up front. A frequent place for this bug is a struct field of map type. The zero value of the struct has a nil map, so initialise it in a constructor function.

Re: Go map returns 0 for a missing key: how do I tell a missing key from a stored zero value?

#5

A related trap with struct values: map elements are not addressable, so devices[id].count++ does not compile when the value type is a struct. Either read, modify and write back, d := devices[id]; d.count++; devices[id] = d, or store pointers in a map[string]*Device. With pointers the increment works, but a missing key yields a nil pointer and the field access panics, so the comma-ok check matters again.

Also, iteration order over a map is not specified and deliberately varies from one range to the next. For stable output, collect the keys into a slice, sort it with sort.Strings(keys) and iterate over that.

Re: Go map returns 0 for a missing key: how do I tell a missing key from a stored zero value?

#6

One more point for anything with goroutines: built-in maps are not safe for concurrent use. One goroutine writing while another reads or writes is a data race, and the runtime often detects it and aborts the program with "fatal error: concurrent map writes", which cannot be recovered.

Protect the map with a sync.RWMutex, taking RLock around reads and Lock around writes, or confine the map to one goroutine and talk to it over channels. sync.Map is meant for specific patterns, such as keys written once and read many times; for ordinary shared state a mutex-guarded map is simpler and keeps static typing. Run the tests with go test -race to find unsynchronised access.

TEP COMMUNITY