Go Maps Tutorial
Maps are Go's built-in associative data type that store key-value pairs. They provide efficient lookups, inserts, and deletes by mapping unique keys to values, making them ideal for dictionaries, caches, and object representations.
1. Map Basics
Maps are declared with the map[KeyType]ValueType syntax and must be initialized before use.
// Declaration
var capitals map[string]string
// Initialization
capitals = make(map[string]string)
// Literal initialization
colors := map[string]string{
"red": "#FF0000",
"green": "#00FF00",
}
// Insert/Update
colors["blue"] = "#0000FF"
// Access
hex := colors["red"] // "#FF0000"
// Delete
delete(colors, "green")
Key Characteristics:
- Keys must be comparable types (no slices, maps, or functions)
- Reference type - points to underlying hash table
- Zero value is
nil(cannot insert into nil map) - Unordered - iteration order is random
Map Basics Quiz
What happens when you access a non-existent key?
2. Map Operations
Go provides several ways to safely and efficiently work with maps.
// Existence check
value, exists := colors["purple"]
if exists {
fmt.Println(value)
}
// Iteration
for key, value := range colors {
fmt.Printf("%s: %s\n", key, value)
}
// Length
count := len(colors) // Number of key-value pairs
// Clear map
clear(colors) // Go 1.21+ (removes all entries)
// Map as set
set := make(map[string]bool)
set["item1"] = true
if set["item1"] {
fmt.Println("item1 exists")
}
Common Patterns:
- Two-value assignment to check existence
- Range loops for iteration (order not guaranteed)
- Using maps as sets with bool values
clear()function (Go 1.21+) to remove all entries
Operations Quiz
How do you check if a key exists in a map?
3. Advanced Techniques
Maps can model complex relationships and behaviors in Go programs.
// Nested maps
population := map[string]map[string]int{
"Europe": {
"France": 67,
"Germany": 83,
},
}
// Maps with slice values
students := map[string][]string{
"classA": {"Alice", "Bob"},
"classB": {"Charlie"},
}
// Concurrent access protection
var mu sync.Mutex
var safeMap = make(map[string]int)
mu.Lock()
safeMap["key"] = 42
mu.Unlock()
// Custom key types
type Coordinate struct{ X, Y int }
grid := make(map[Coordinate]string)
grid[Coordinate{1,2}] = "value"
Advanced Uses:
- Nested maps for hierarchical data
- Slices as values for one-to-many relationships
- Mutexes for concurrent access (or use sync.Map)
- Structs as keys (must be comparable)
Advanced Quiz
What's required for a struct to be used as a map key?
4. Thread-Safe Maps
The sync.Map provides concurrent access without explicit locking.
var sm sync.Map
// Store values
sm.Store("key1", 100)
sm.Store("key2", 200)
// Load values
if value, ok := sm.Load("key1"); ok {
fmt.Println(value) // 100
}
// Atomic operations
sm.LoadOrStore("key3", 300)
// Iteration
sm.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true // continue iteration
})
When to Use sync.Map:
- High concurrency scenarios with many goroutines
- When keys are stable (few inserts/deletes after initialization)
- For write-once, read-many patterns
- Not generally faster than mutex-protected maps - benchmark first
sync.Map Quiz
When should you prefer sync.Map over mutex-protected maps?
5. Performance Optimization
Understanding map internals helps write efficient Go code.
// Pre-allocate with expected size
m := make(map[string]int, 1000)
// Pointer values reduce copying
type LargeStruct struct{ /* many fields */ }
pointers := make(map[int]*LargeStruct)
// Reuse maps to avoid allocations
var cache map[string]Result
clear(cache) // Reuse instead of making new map
// Compare map[string] vs map[[]byte]
// (Convert keys when needed)
bytesMap := make(map[[16]byte]string) // Array keys
Optimization Tips:
- Pre-size maps with
makewhen size is known - Use pointers for large values to avoid copying
- Consider
stringvs[]bytekey tradeoffs - Benchmark map alternatives (slices, custom hash tables) for extreme cases
Performance Quiz
Why pre-size a map with make?
6. Common Map Patterns
Idiomatic Go uses maps in several standard patterns.
// Counting occurrences
counts := make(map[string]int)
for _, item := range items {
counts[item]++
}
// Grouping
groups := make(map[string][]Person)
for _, p := range people {
groups[p.City] = append(groups[p.City], p)
}
// Caching
var cache = make(map[string]ExpensiveResult)
func getCached(key string) ExpensiveResult {
if result, ok := cache[key]; ok {
return result
}
result := computeExpensiveResult(key)
cache[key] = result
return result
}
// Default values
defaults := map[string]int{
"timeout": 30,
"port": 8080,
}
value := defaults["timeout"] // 30
Practical Applications:
- Frequency counting and histograms
- Grouping and categorization
- Memoization and caching
- Configuration defaults
- Set operations (using
map[T]bool)
Patterns Quiz
What's the most efficient way to implement a set in Go?
7. Maps and JSON
Maps work seamlessly with JSON encoding/decoding in Go.
// JSON to map
jsonStr := `{"name":"Alice","age":25}`
var data map[string]interface{}
json.Unmarshal([]byte(jsonStr), &data)
name := data["name"].(string)
// Map to JSON
config := map[string]any{
"timeout": 30,
"features": []string{"auth", "logging"},
}
jsonData, _ := json.Marshal(config)
// Structured decoding
var person map[string]interface{}
json.Unmarshal([]byte(jsonStr), &person)
age := int(person["age"].(float64)) // JSON numbers are float64
JSON Handling Notes:
map[string]interface{}can hold arbitrary JSON- Use type assertions to access values
- JSON numbers decode as
float64by default - For structured data, prefer structs over maps
JSON Quiz
Why use map[string]interface{} for JSON parsing?