Go Interview Prep #5: Maps

Maps are Go’s built-in hash table. They are fast and easy to use, but they have several sharp corners that interviews frequently probe.

Creating a map

// Using make (preferred for non-literal maps)
m := make(map[string]int)

// Optional size hint (capacity is not a hard limit)
m := make(map[string]int, 100)

// Map literal
m := map[string]int{"a": 1, "b": 2}

// Zero value — nil, not empty
var m map[string]int // m == nil

The size hint to make reduces reallocations when you know the approximate number of keys up front; it is not a maximum.

Nil map behavior

A nil map reads like an empty map but panics on write:

var m map[string]int
_ = m["key"]  // ok: returns 0
m["key"] = 1  // panic: assignment to entry in nil map

Always initialize a map before writing to it. len(nilMap) is 0; ranging over a nil map is a no-op.

The comma-ok idiom

A map lookup returns a zero value when the key is absent. Use the two-value form to distinguish “key not present” from “key present with zero value”:

v, ok := m["key"]
if !ok {
    // key does not exist
}

Deletion

delete(m, "key") // safe even if key doesn't exist; no-op on nil map

Iteration order is random

Go deliberately randomizes map iteration order to prevent code from depending on it:

for k, v := range m {
    fmt.Println(k, v) // order varies each run
}

If you need sorted output, collect the keys into a slice and sort them first (slices.Sort / sort.Strings).

You may safely delete the current key during a range loop; deleting other keys or adding keys has unspecified effects on remaining iteration.

Key types must be comparable

Only types that support == can be map keys: booleans, numbers, strings, pointers, channels, interfaces, arrays, and structs (if all fields are comparable). Slices, maps, and functions cannot be keys.

Interface keys are allowed, but if the dynamic type is incomparable (e.g. a slice), using that key panics at runtime. Prefer concrete comparable key types.

Maps are not safe for concurrent use

Concurrent reads of a map that is not being written are fine. A concurrent read and write — or two concurrent writes — triggers a fatal runtime error (concurrent map read and map write / concurrent map writes). That is not a recover-able panic; the process exits.

// Detected as a fatal concurrent map access (and by the race detector)
go func() { m["x"] = 1 }()
go func() { _ = m["x"] }()

Options:

  • Wrap with sync.Mutex / sync.RWMutex for general use.
  • Use sync.Map for read-heavy, write-once (or disjoint-key) workloads.
  • Shard the map (multiple maps + locks) under high contention.

Map internals (brief)

Go maps are hash tables with bucket-based chaining (8 key/value slots per bucket, overflow buckets as needed). Average lookup, insert, and delete are amortized O(1). The map grows when the load factor exceeds about 6.5 entries per bucket; existing entries are incrementally evacuated to the new buckets during subsequent operations (so a single insert can do a bit of extra work, not a full stop-the-world copy of the whole map).

Common interview questions

Q: What happens when you read a missing key?
You get the zero value for the value type — 0 for int, "" for string, nil for pointers and slices. Use the comma-ok form to distinguish absence from a stored zero.

Q: Why is map iteration order random?
Intentional design decision: it forces developers not to rely on ordering. In early Go versions iteration order was accidentally deterministic, which led to bugs when the runtime changed.

Q: Can you take the address of a map value?
No. Map values are not addressable (&m[k] does not compile). To mutate a struct stored as a map value, read it out, modify it, and write it back — or store pointers (map[string]*Counter).

type Counter struct{ N int }
m := map[string]Counter{}
c := m["x"]
c.N++
m["x"] = c

Q: How do you implement a set in Go?
Use map[T]struct{}. Empty struct{} values consume no memory.

set := make(map[string]struct{})
set["go"] = struct{}{}
_, exists := set["go"]

Q: What is the difference between a nil map and an empty map?
Both have len == 0 and support reads. Only the empty (initialized) map allows writes. Prefer make or a literal before first write; return nil maps from functions when “no data” is fine for read-only callers.

Quick reference

OperationBehavior
m[k] (missing key)Returns zero value
v, ok := m[k]ok is false if key absent
delete(m, k)Safe even if key absent
Nil map readReturns zero value
Nil map writePanic
Concurrent read+writeFatal runtime error (use mutex or sync.Map)
Iteration orderRandomized
Map value addressNot addressable