Maps

Updated

July 30, 2026

Overview

Maps are Go’s built-in hash table type, providing O(1) average-time lookups, insertions, and deletions.

Creating Maps

var m map[string]int           // nil map (read-only!)
m := make(map[string]int)      // Empty, ready to use
m := make(map[string]int, 100) // With capacity hint
m := map[string]int{           // Literal
    "one": 1,
    "two": 2,
}

Basic Operations

Set

m["key"] = value

Get

val := m["key"]  // Returns zero value if missing

Delete

delete(m, "key")  // No-op if key doesn't exist

Check Existence

val, ok := m["key"]
if ok {
    // Key exists
}

if _, exists := m["key"]; exists {
    // Key exists
}

nil Map Behavior

var m map[string]int  // nil

val := m["key"]       // OK: returns 0
m["key"] = 1          // PANIC! Cannot write to nil map

Always initialize before writing:

m := make(map[string]int)
m["key"] = 1  // OK

Iteration

for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

for key := range m {  // Keys only
    fmt.Println(key)
}

Warning: Iteration order is randomized!

Common Patterns

Set (Unique Values)

set := make(map[string]struct{})
set["item"] = struct{}{}
if _, exists := set["item"]; exists {
    // Item is in set
}
delete(set, "item")

Counter

counter := make(map[string]int)
for _, word := range words {
    counter[word]++  // Zero value works!
}

Grouping

groups := make(map[string][]User)
for _, user := range users {
    groups[user.Country] = append(groups[user.Country], user)
}

Default Value

val := m["key"]
if val == 0 {
    val = defaultValue
}

Concurrency Warning

Maps are not goroutine-safe:

// Unsafe: concurrent read/write
go func() { m["key"] = 1 }()
go func() { _ = m["key"] }()

// Use sync.Map or mutex
var mu sync.Mutex
mu.Lock()
m["key"] = 1
mu.Unlock()

The maps Package (Go 1.21+)

The standard library maps package provides generic utilities for common operations.

import "maps"

maps.Clone(m)                 // Shallow copy
maps.Equal(m1, m2)            // Compare
maps.DeleteFunc(m, func(k K, v V) bool {
    return v < 10             // Conditional delete
})

Modern Loop Pattern (Go 1.23+)

Using iterators with maps:

for k, v := range maps.All(m) { /* ... */ }
for k := range maps.Keys(m) { /* ... */ }
for v := range maps.Values(m) { /* ... */ }

Map Internals

  • Keys must be comparable (== must work)
  • Valid key types: bool, numeric, string, pointer, channel, interface, structs/arrays of comparable types
  • Invalid: slices, maps, functions

Summary

Operation Syntax
Create make(map[K]V)
Set m[key] = value
Get val := m[key]
Check val, ok := m[key]
Delete delete(m, key)
Length len(m)

More examples

Example: comma-ok lookup

Save as main.go and go run . (with go mod init example if needed).

package main

import "fmt"

func main() {
    m := map[string]int{"a": 1}
    if v, ok := m["a"]; ok {
        fmt.Println("a:", v)
    }
    if _, ok := m["missing"]; !ok {
        fmt.Println("missing: not present")
    }
}

Expected:

a: 1
missing: not present

Example: delete and range

Save as main.go and go run . (with go mod init example if needed).

package main

import "fmt"

func main() {
    m := map[string]int{"x": 10, "y": 20}
    delete(m, "x")
    fmt.Println("len:", len(m))
    for k, v := range m {
        fmt.Printf("%s=%d\n", k, v)
    }
}

Expected:

len: 1
y=20

Runnable example

Save as main.go. From an empty directory:

go mod init example
go run .
package main

import (
    "fmt"
    "maps"
)

func main() {
    // nil map: read ok, write panics — initialize first
    var nilMap map[string]int
    fmt.Println("nil read:", nilMap["x"])

    m := make(map[string]int)
    m["one"] = 1
    m["two"] = 2
    fmt.Println("m:", m, "len:", len(m))

    if v, ok := m["two"]; ok {
        fmt.Println("found two:", v)
    }
    if _, ok := m["three"]; !ok {
        fmt.Println("three missing")
    }

    delete(m, "one")
    fmt.Println("after delete one:", m)

    // Word counter uses zero value on missing keys
    counter := make(map[string]int)
    for _, w := range []string{"go", "is", "go", "fun", "go"} {
        counter[w]++
    }
    fmt.Println("counts:", counter)

    // Set via map[T]struct{}
    set := make(map[string]struct{})
    set["alpha"] = struct{}{}
    set["beta"] = struct{}{}
    if _, ok := set["alpha"]; ok {
        fmt.Println("set has alpha")
    }

    // Grouping
    type User struct{ Name, Country string }
    users := []User{
        {"Ada", "UK"},
        {"Linus", "FI"},
        {"Alan", "UK"},
    }
    groups := make(map[string][]string)
    for _, u := range users {
        groups[u.Country] = append(groups[u.Country], u.Name)
    }
    fmt.Println("groups:", groups)

    // maps package (Go 1.21+)
    clone := maps.Clone(counter)
    clone["fun"] = 99
    fmt.Println("original fun:", counter["fun"], "clone fun:", clone["fun"])
    fmt.Println("equal?", maps.Equal(counter, clone))

    fmt.Println("keys (order random):")
    for k, v := range counter {
        fmt.Printf("  %s=%d\n", k, v)
    }
}

Expected output (illustrative; key order varies):

nil read: 0
m: map[one:1 two:2] len: 2
found two: 2
three missing
after delete one: map[two:2]
counts: map[fun:1 go:3 is:1]
set has alpha
groups: map[FI:[Linus] UK:[Ada Alan]]
original fun: 1 clone fun: 99
equal? false
keys (order random):
  go=3
  is=1
  fun=1

What to notice: - Comma-ok distinguishes missing keys from zero values. - counter[w]++ works because missing keys read as 0. - map[T]struct{} is an idiomatic set with near-zero value size. - Iteration order is deliberately random.

Try next: Uncomment a write to nilMap["x"] = 1 once to see the panic, then remove it.