Skip to main content

Maps

What Is a Map?​

A map is a hash-table-based data structure that stores key-value pairs. Values are looked up by key in O(1) time, making maps ideal for fast lookups.

Go maps are reference types β€” passing a map to a function shares the underlying hash table. Writing to a nil map causes a runtime panic.


Declaring and Initialising Maps​

package main

import "fmt"

func main() {
// 1. nil map (reads are safe, writes panic)
var m1 map[string]int
fmt.Println(m1 == nil) // true
fmt.Println(m1["key"]) // 0 β€” reading returns the zero value
// m1["key"] = 1 // runtime panic!

// 2. make (recommended)
m2 := make(map[string]int)
m2["alice"] = 90
m2["bob"] = 85
fmt.Println(m2) // map[alice:90 bob:85]

// 3. Map literal
m3 := map[string]int{
"alice": 90,
"bob": 85,
"carol": 78,
}
fmt.Println(m3)

// 4. Capacity hint (performance optimisation)
m4 := make(map[string]int, 100)
_ = m4
}

CRUD Operations​

package main

import "fmt"

func main() {
scores := map[string]int{
"alice": 90,
"bob": 85,
}

// Create / Update β€” same syntax
scores["carol"] = 78 // new key
scores["alice"] = 95 // update existing key

// Read
fmt.Println(scores["alice"]) // 95
fmt.Println(scores["none"]) // 0 (non-existent key β†’ zero value)

// Check key existence (comma-ok idiom)
val, ok := scores["bob"]
if ok {
fmt.Printf("bob's score: %d\n", val)
}

_, exists := scores["dave"]
fmt.Println("dave exists:", exists) // false

// Delete
delete(scores, "bob")
fmt.Println(scores) // map[alice:95 carol:78]

// Deleting a non-existent key is a no-op
delete(scores, "nobody")
}

Iterating Over a Map​

Map iteration order is random on every run. Sort the keys if order matters.

package main

import (
"fmt"
"sort"
)

func main() {
m := map[string]int{
"banana": 3,
"apple": 5,
"cherry": 2,
}

// Default iteration β€” order not guaranteed
for k, v := range m {
fmt.Printf("%s: %d\n", k, v)
}

fmt.Println("--- sorted ---")

// Sorted iteration
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, m[k])
}
// apple: 5
// banana: 3
// cherry: 2
}

Nested Maps and Complex Value Types​

package main

import "fmt"

func main() {
// Map with slice values (grouping)
groups := map[string][]string{
"backend": {"alice", "bob"},
"frontend": {"carol", "dave"},
}
groups["backend"] = append(groups["backend"], "eve")
fmt.Println(groups)
// map[backend:[alice bob eve] frontend:[carol dave]]

// Nested maps β€” inner maps must be initialised explicitly
nested := map[string]map[string]int{}
nested["team1"] = map[string]int{"alice": 90, "bob": 85}
nested["team2"] = map[string]int{"carol": 78}
fmt.Println(nested["team1"]["alice"]) // 90

// Bad pattern β€” writing to uninitialised inner map panics
// var bad map[string]map[string]int
// bad["k1"]["k2"] = 1 // panic!
}

Maps with Struct Values​

In real projects, struct values in maps are very common.

package main

import "fmt"

type Student struct {
Name string
Score int
Grade string
}

func toGrade(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 70:
return "C"
default:
return "F"
}
}

func main() {
students := map[string]Student{
"s001": {Name: "Alice", Score: 92},
"s002": {Name: "Bob", Score: 78},
"s003": {Name: "Carol", Score: 85},
}

// Updating a struct field: must extract, modify, and replace
s := students["s001"]
s.Grade = toGrade(s.Score)
students["s001"] = s

for id, st := range students {
fmt.Printf("[%s] %s: %d (%s)\n", id, st.Name, st.Score, st.Grade)
}
}

Note:students["s001"].Grade = "A" does not compile β€” map values are not addressable. Extract the value, modify it, and put it back. Using a pointer (map[string]*Student) allows direct field modification.


Concurrency Caution​

The built-in map is not goroutine-safe. Concurrent reads and writes cause a runtime panic.

package main

import (
"fmt"
"sync"
)

type SafeMap struct {
mu sync.RWMutex
m map[string]int
}

func NewSafeMap() *SafeMap {
return &SafeMap{m: make(map[string]int)}
}

func (sm *SafeMap) Set(key string, val int) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.m[key] = val
}

func (sm *SafeMap) Get(key string) (int, bool) {
sm.mu.RLock()
defer sm.mu.RUnlock()
v, ok := sm.m[key]
return v, ok
}

func main() {
sm := NewSafeMap()

var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
key := fmt.Sprintf("key%d", n%10)
sm.Set(key, n)
}(i)
}
wg.Wait()

if v, ok := sm.Get("key0"); ok {
fmt.Println("key0:", v)
}

// sync.Map β€” goroutine-safe, optimised for read-heavy workloads
var syncMap sync.Map
syncMap.Store("hello", 42)
if val, ok := syncMap.Load("hello"); ok {
fmt.Println(val.(int)) // 42
}
syncMap.Range(func(k, v any) bool {
fmt.Printf("%v: %v\n", k, v)
return true
})
}

Practical Example β€” Word Frequency Counter​

package main

import (
"fmt"
"sort"
"strings"
)

type WordCount struct {
Word string
Count int
}

func topN(text string, n int) []WordCount {
freq := make(map[string]int)
for _, word := range strings.Fields(strings.ToLower(text)) {
word = strings.Trim(word, ".,!?;:")
if word != "" {
freq[word]++
}
}

counts := make([]WordCount, 0, len(freq))
for word, count := range freq {
counts = append(counts, WordCount{word, count})
}

sort.Slice(counts, func(i, j int) bool {
if counts[i].Count != counts[j].Count {
return counts[i].Count > counts[j].Count
}
return counts[i].Word < counts[j].Word
})

if n > len(counts) {
n = len(counts)
}
return counts[:n]
}

func main() {
text := "go is great go is fast go is simple and go is powerful"
for _, wc := range topN(text, 5) {
fmt.Printf("%-10s %d\n", wc.Word, wc.Count)
}
// go 4
// is 4
// and 1
// fast 1
// great 1
}