Web Analytics

Maps

Beginner ~35 min read

Maps are Go's built-in hash table type, providing fast key-value lookups. They're essential for many programming tasks, from counting occurrences to caching data. In this lesson, you'll learn how to create, use, and manipulate maps effectively.

What is a Map?

A map is an unordered collection of key-value pairs. Each key is unique and maps to a value:

Output
Click Run to execute your code
Map Characteristics:
  • Unordered - Iteration order is random
  • Reference type - Like slices, maps are references
  • Dynamic - Can grow as needed
  • Fast lookups - O(1) average time complexity

Creating Maps

// Map literal
ages := map[string]int{
    "Alice": 25,
    "Bob":   30,
    "Carol": 28,
}

// Using make
scores := make(map[string]int)

// Empty map literal
empty := map[string]int{}

// Nil map (can't add to it!)
var nilMap map[string]int  // nil
Important: A nil map can be read from but not written to. Always initialize maps with make() or a map literal before adding elements!

Map Operations

Adding and Updating

ages := make(map[string]int)

// Add new key-value pair
ages["Alice"] = 25

// Update existing value
ages["Alice"] = 26

// Add multiple
ages["Bob"] = 30
ages["Carol"] = 28

Retrieving Values

Output
Click Run to execute your code
The "comma ok" Idiom:
value, ok := myMap[key]
if ok {
    // Key exists, use value
} else {
    // Key doesn't exist
}
This is the idiomatic way to check if a key exists in Go!

Deleting Keys

ages := map[string]int{
    "Alice": 25,
    "Bob":   30,
}

// Delete a key
delete(ages, "Alice")

// Delete non-existent key (safe, no error)
delete(ages, "NonExistent")

fmt.Println(ages)  // map[Bob:30]

Iterating Over Maps

Use range to iterate over maps:

Output
Click Run to execute your code
Remember: Map iteration order is random! Don't rely on any specific order. If you need ordered keys, sort them first.

Sorting Map Keys

import "sort"

ages := map[string]int{
    "Carol": 28,
    "Alice": 25,
    "Bob":   30,
}

// Get keys
keys := make([]string, 0, len(ages))
for k := range ages {
    keys = append(keys, k)
}

// Sort keys
sort.Strings(keys)

// Iterate in sorted order
for _, k := range keys {
    fmt.Printf("%s: %d\n", k, ages[k])
}
// Output:
// Alice: 25
// Bob: 30
// Carol: 28

Using Maps as Sets

Go doesn't have a built-in set type, but maps can be used as sets:

// Set of strings
set := make(map[string]bool)

// Add elements
set["apple"] = true
set["banana"] = true
set["orange"] = true

// Check membership
if set["apple"] {
    fmt.Println("apple is in the set")
}

// Remove element
delete(set, "banana")

// Iterate over set
for item := range set {
    fmt.Println(item)
}

// Alternative: use empty struct (saves memory)
set2 := make(map[string]struct{})
set2["apple"] = struct{}{}
_, exists := set2["apple"]  // true
Pro Tip: Use map[string]struct{}{} for sets instead of map[string]bool to save memory. Empty structs take zero bytes!
Go Maps Structure - Hash Table with Buckets

Figure: Go Maps - Hash table structure with buckets and common operations

Practical Map Patterns

1. Counting Occurrences

words := []string{"apple", "banana", "apple", "orange", "banana", "apple"}

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

fmt.Println(counts)  // map[apple:3 banana:2 orange:1]

2. Grouping Data

type Person struct {
    Name string
    Age  int
}

people := []Person{
    {"Alice", 25},
    {"Bob", 30},
    {"Carol", 25},
    {"Dave", 30},
}

// Group by age
byAge := make(map[int][]Person)
for _, p := range people {
    byAge[p.Age] = append(byAge[p.Age], p)
}

// byAge[25] = [Alice, Carol]
// byAge[30] = [Bob, Dave]

3. Caching/Memoization

var cache = make(map[int]int)

func fibonacci(n int) int {
    // Check cache
    if val, ok := cache[n]; ok {
        return val
    }
    
    // Calculate
    var result int
    if n <= 1 {
        result = n
    } else {
        result = fibonacci(n-1) + fibonacci(n-2)
    }
    
    // Store in cache
    cache[n] = result
    return result
}

Common Mistakes

1. Writing to nil map

// ❌ Wrong - panic: assignment to entry in nil map
var ages map[string]int
ages["Alice"] = 25  // Runtime panic!

// βœ… Correct - initialize first
ages := make(map[string]int)
ages["Alice"] = 25

// Or use map literal
ages := map[string]int{}
ages["Alice"] = 25

2. Not checking if key exists

// ❌ Wrong - can't distinguish between zero value and missing key
ages := map[string]int{"Alice": 0}
age := ages["Bob"]  // 0 (zero value)
// Is Bob's age 0, or does the key not exist?

// βœ… Correct - use comma ok idiom
if age, ok := ages["Bob"]; ok {
    fmt.Println("Bob's age:", age)
} else {
    fmt.Println("Bob not found")
}

3. Relying on iteration order

// ❌ Wrong - order is random!
ages := map[string]int{
    "Alice": 25,
    "Bob":   30,
    "Carol": 28,
}
for name := range ages {
    fmt.Println(name)  // Random order each time!
}

// βœ… Correct - sort keys if order matters
keys := make([]string, 0, len(ages))
for k := range ages {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, ages[k])
}

4. Concurrent map access

// ❌ Wrong - concurrent map writes cause panic
m := make(map[int]int)
go func() { m[1] = 1 }()
go func() { m[2] = 2 }()  // Race condition!

// βœ… Correct - use sync.Mutex or sync.Map
var mu sync.Mutex
m := make(map[int]int)

go func() {
    mu.Lock()
    m[1] = 1
    mu.Unlock()
}()

Exercise: Word Frequency Counter

Task: Create a word frequency counter.

Requirements:

  • Count word frequencies in a sentence
  • Find the most common word
  • Print words in alphabetical order with counts
  • Test with: "the quick brown fox jumps over the lazy dog the fox"
Show Solution
package main

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

func main() {
    text := "the quick brown fox jumps over the lazy dog the fox"
    
    // Split into words
    words := strings.Fields(text)
    
    // Count frequencies
    freq := make(map[string]int)
    for _, word := range words {
        freq[word]++
    }
    
    // Find most common word
    maxCount := 0
    mostCommon := ""
    for word, count := range freq {
        if count > maxCount {
            maxCount = count
            mostCommon = word
        }
    }
    
    fmt.Printf("Most common word: '%s' (appears %d times)\n\n", mostCommon, maxCount)
    
    // Get sorted keys
    keys := make([]string, 0, len(freq))
    for k := range freq {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    
    // Print in alphabetical order
    fmt.Println("Word frequencies (alphabetical):")
    for _, word := range keys {
        fmt.Printf("  %s: %d\n", word, freq[word])
    }
    
    // Bonus: Total unique words
    fmt.Printf("\nTotal unique words: %d\n", len(freq))
    fmt.Printf("Total words: %d\n", len(words))
}

Summary

  • Maps store key-value pairs with fast lookups
  • Create maps with make() or map literals
  • Nil maps can be read but not written to
  • Comma ok idiom checks if a key exists
  • delete() removes keys from maps
  • Iteration order is random - sort keys if needed
  • Maps are reference types - passed by reference
  • Not safe for concurrent use - use mutex or sync.Map

What's Next?

Now that you understand maps, you're ready to learn about Structs & Methods. Structs let you create custom types, and methods let you add behavior to those typesβ€”the foundation of object-oriented programming in Go!