Why This Matters - The Foundation of Key-Value Storage
Maps are essential data structures that enable fast lookups, caching, and data relationships. They're the backbone of countless applications - from web servers routing requests to databases indexing records.
Understanding maps is crucial because:
- Fast Lookups: O(1) average time complexity for data retrieval
- Flexible Keys: Support various comparable types as keys
- Dynamic Nature: Add and remove entries at runtime
- Memory Efficiency: Store only what you need, grow as needed
- Real-World Modeling: Perfect for representing relationships and configurations
Real-World Impact: Every web framework uses maps for routing, every caching system relies on maps for storage, every JSON parser uses maps for dynamic data, and every configuration system uses maps for settings. Mastering maps means you can build efficient, scalable applications.
Learning Objectives
By the end of this article, you will:
- ✅ Understand map internals and hash table implementation
- ✅ Master key requirements and comparison rules
- ✅ Implement efficient lookup patterns and avoid common pitfalls
- ✅ Use map literals and built-in functions effectively
- ✅ Handle concurrent access safely and correctly
- ✅ Apply performance optimization techniques
- ✅ Build real-world applications with map-based patterns
- ✅ Debug common map-related issues in production code
Core Concepts - Understanding Map Fundamentals
Hash Tables Under the Hood
Maps are implemented as hash tables - collections of key-value pairs where keys are hashed to determine storage location.
1// Conceptual map structure
2type Map struct {
3 buckets []bucket // Array of buckets
4 count int // Number of stored elements
5 seed uintptr // Hash seed for security
6}
7
8type bucket struct {
9 tophash [8]uint8 // Top 8 bits of hash
10 entries []entry // Key-value pairs
11}
12
13type entry struct {
14 key KeyType
15 value ValueType
16}
Key Characteristics:
- Dynamic sizing: Grows when load factor gets too high
- Collision resolution: Handles hash collisions efficiently
- Security: Randomized hash seeds prevent DoS attacks
- Memory overhead: More memory than simple arrays but provides flexibility
Map Declaration and Initialization
1// Declaration styles
2var m1 map[string]int // nil map
3m2 := make(map[string]int) // empty map
4m3 := map[string]int{"go": 1, "python": 2} // literal with data
5m4 := make(map[string]int, 1000) // pre-allocate capacity
Why Different Methods:
var m map[K]Vcreates nil map - needsmake()before usemake(map[K]V)creates empty, ready-to-use mapmap[K]V{}literal creates map with initial datamake(map[K]V, cap)pre-allocates for performance
Understanding Map Keys
Not all types can be map keys. Keys must be comparable - meaning they support the == operator.
Valid Key Types:
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Integer keys
8 ages := map[int]string{
9 1: "one",
10 2: "two",
11 3: "three",
12 }
13 fmt.Println("Integer keys:", ages)
14
15 // String keys (most common)
16 capitals := map[string]string{
17 "USA": "Washington DC",
18 "France": "Paris",
19 "Japan": "Tokyo",
20 }
21 fmt.Println("String keys:", capitals)
22
23 // Struct keys (all fields must be comparable)
24 type Point struct {
25 X, Y int
26 }
27 distances := map[Point]float64{
28 {0, 0}: 0.0,
29 {1, 0}: 1.0,
30 {0, 1}: 1.0,
31 }
32 fmt.Println("Struct keys:", distances)
33
34 // Array keys (arrays are comparable if their elements are)
35 type RGB [3]uint8
36 colors := map[RGB]string{
37 {255, 0, 0}: "red",
38 {0, 255, 0}: "green",
39 {0, 0, 255}: "blue",
40 }
41 fmt.Println("Array keys:", colors)
42
43 // Pointer keys
44 x, y := 10, 20
45 ptrMap := map[*int]string{
46 &x: "pointer to x",
47 &y: "pointer to y",
48 }
49 fmt.Println("Pointer keys:", ptrMap)
50}
Invalid Key Types:
1// ❌ These won't compile
2var m1 map[[]int]string // Slices are not comparable
3var m2 map[map[string]int]bool // Maps are not comparable
4var m3 map[func()]int // Functions are not comparable
The Zero Value Behavior
Maps have special zero-value behavior that differs from other types:
1// run
2package main
3
4import "fmt"
5
6func main() {
7 var nilMap map[string]int // nil map
8 emptyMap := make(map[string]int) // empty but non-nil map
9
10 fmt.Printf("nilMap == nil: %t\n", nilMap == nil)
11 fmt.Printf("emptyMap == nil: %t\n", emptyMap == nil)
12
13 // Reading from nil map returns zero value (no panic)
14 value := nilMap["key"]
15 fmt.Printf("Reading from nil map: %d\n", value)
16
17 // Checking existence works on nil maps
18 _, exists := nilMap["key"]
19 fmt.Printf("Key exists in nil map: %t\n", exists)
20
21 // Length of nil map is 0
22 fmt.Printf("Length of nil map: %d\n", len(nilMap))
23
24 // ❌ Writing to nil map causes panic!
25 // nilMap["key"] = 42 // This would crash
26
27 // ✅ Writing to empty map works fine
28 emptyMap["key"] = 42
29 fmt.Printf("Empty map after write: %v\n", emptyMap)
30}
Important: You can read from a nil map, but writing to it causes a panic. Always initialize maps before writing!
Practical Examples - Map Operations in Action
Example 1: Basic Map Operations
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Map creation and initialization
8 prices := map[string]float64{
9 "apple": 0.99,
10 "banana": 0.59,
11 "cherry": 2.99,
12 }
13
14 fmt.Println("Initial prices:", prices)
15
16 // Adding/updating entries
17 prices["orange"] = 1.49 // Add new entry
18 prices["apple"] = 1.09 // Update existing entry
19 fmt.Println("After updates:", prices)
20
21 // Reading entries
22 applePrice, exists := prices["apple"]
23 if exists {
24 fmt.Printf("Apple price: $%.2f\n", applePrice)
25 }
26
27 grapePrice, exists := prices["grape"]
28 if !exists {
29 fmt.Println("Grape not found in price list")
30 }
31
32 // Deleting entries
33 delete(prices, "banana")
34 fmt.Println("After deleting banana:", prices)
35
36 // Map properties
37 fmt.Printf("Map length: %d\n", len(prices))
38}
Example 2: Map Value Types and Behavior
1// run
2package main
3
4import "fmt"
5
6type Address struct {
7 Street string
8 City string
9 Country string
10}
11
12func main() {
13 // Map with slice values
14 users := map[string][]string{
15 "alice": {"reading", "hiking"},
16 "bob": {"gaming", "coding"},
17 "charlie": {"music"},
18 }
19
20 // Adding to slice in map
21 users["alice"] = append(users["alice"], "cooking")
22 fmt.Printf("Alice's hobbies: %v\n", users["alice"])
23
24 // Map with struct values
25 addresses := make(map[string]Address)
26 addresses["home"] = Address{
27 Street: "123 Main St",
28 City: "Anytown",
29 Country: "USA",
30 }
31
32 // Modifying struct in map requires reassignment
33 home := addresses["home"]
34 home.City = "Newtown"
35 addresses["home"] = home
36 fmt.Printf("Home address: %+v\n", addresses["home"])
37
38 // Map with pointer values
39 profiles := make(map[string]*Address)
40 profiles["work"] = &Address{
41 Street: "456 Office Blvd",
42 City: "Business City",
43 Country: "USA",
44 }
45
46 // Can modify directly through pointer
47 profiles["work"].Street = "789 Corporate Ave"
48 fmt.Printf("Work address: %+v\n", *profiles["work"])
49}
Example 3: Map Iteration and Key Order
1// run
2package main
3
4import (
5 "fmt"
6 "sort"
7)
8
9func main() {
10 // Create a map
11 scores := map[string]int{
12 "alice": 95,
13 "bob": 87,
14 "charlie": 92,
15 "diana": 88,
16 "eve": 91,
17 }
18
19 fmt.Println("Map iteration (random order):")
20 for name, score := range scores {
21 fmt.Printf("%s: %d\n", name, score)
22 }
23
24 // Sorted iteration by keys
25 fmt.Println("\nSorted by name:")
26 names := make([]string, 0, len(scores))
27 for name := range scores {
28 names = append(names, name)
29 }
30 sort.Strings(names)
31
32 for _, name := range names {
33 fmt.Printf("%s: %d\n", name, scores[name])
34 }
35
36 // Sorted iteration by values
37 fmt.Println("\nSorted by score:")
38 type Score struct {
39 name string
40 score int
41 }
42
43 sortedScores := make([]Score, 0, len(scores))
44 for name, score := range scores {
45 sortedScores = append(sortedScores, Score{name, score})
46 }
47
48 // Sort by score (descending)
49 sort.Slice(sortedScores, func(i, j int) bool {
50 return sortedScores[i].score > sortedScores[j].score
51 })
52
53 for _, item := range sortedScores {
54 fmt.Printf("%s: %d\n", item.name, item.score)
55 }
56}
Important Note: Map iteration order is intentionally randomized in Go to prevent code from depending on a specific order. If you need ordered iteration, extract keys to a slice and sort them first.
Example 4: Nested Maps and Complex Data
1// run
2package main
3
4import "fmt"
5
6type Address struct {
7 Street string
8 City string
9 Country string
10}
11
12func main() {
13 // Nested maps: organizing hierarchical data
14 company := map[string]map[string]float64{
15 "engineering": {
16 "alice": 95000,
17 "bob": 87000,
18 "charlie": 102000,
19 },
20 "marketing": {
21 "diana": 78000,
22 "eve": 82000,
23 },
24 "sales": {
25 "frank": 88000,
26 },
27 }
28
29 // Accessing nested data
30 engineering := company["engineering"]
31 aliceSalary := engineering["alice"]
32 fmt.Printf("Alice's salary: $%.2f\n", aliceSalary)
33
34 // Adding to nested structure
35 if _, exists := company["hr"]; !exists {
36 company["hr"] = make(map[string]float64)
37 }
38 company["hr"]["grace"] = 72000
39
40 // Calculating department totals
41 for dept, employees := range company {
42 var total float64
43 for _, salary := range employees {
44 total += salary
45 }
46 fmt.Printf("%s department: %d employees, $%.2f total\n",
47 dept, len(employees), total)
48 }
49
50 // Complex data structure example
51 type Student struct {
52 Name string
53 Grades map[string]float64
54 Address Address
55 }
56
57 students := make(map[int]Student)
58 students[1001] = Student{
59 Name: "John Doe",
60 Grades: map[string]float64{
61 "math": 95.5,
62 "science": 87.3,
63 "english": 92.1,
64 },
65 Address: Address{
66 Street: "123 University Ave",
67 City: "College Town",
68 Country: "USA",
69 },
70 }
71
72 student := students[1001]
73 fmt.Printf("\nStudent %s's math grade: %.1f\n",
74 student.Name, student.Grades["math"])
75}
Example 5: Map as Set Implementation
1// run
2package main
3
4import "fmt"
5
6// Set implementation using map
7type Set[T comparable] struct {
8 items map[T]struct{}
9}
10
11func NewSet[T comparable]() *Set[T] {
12 return &Set[T]{
13 items: make(map[T]struct{}),
14 }
15}
16
17func (s *Set[T]) Add(item T) {
18 s.items[item] = struct{}{}
19}
20
21func (s *Set[T]) Remove(item T) {
22 delete(s.items, item)
23}
24
25func (s *Set[T]) Contains(item T) bool {
26 _, exists := s.items[item]
27 return exists
28}
29
30func (s *Set[T]) Size() int {
31 return len(s.items)
32}
33
34func (s *Set[T]) Items() []T {
35 items := make([]T, 0, len(s.items))
36 for item := range s.items {
37 items = append(items, item)
38 }
39 return items
40}
41
42// Set operations
43func (s *Set[T]) Union(other *Set[T]) *Set[T] {
44 result := NewSet[T]()
45
46 // Add all items from this set
47 for item := range s.items {
48 result.Add(item)
49 }
50
51 // Add all items from other set
52 for item := range other.items {
53 result.Add(item)
54 }
55
56 return result
57}
58
59func (s *Set[T]) Intersection(other *Set[T]) *Set[T] {
60 result := NewSet[T]()
61
62 // Add only items that exist in both sets
63 for item := range s.items {
64 if other.Contains(item) {
65 result.Add(item)
66 }
67 }
68
69 return result
70}
71
72func main() {
73 // Integer set example
74 numbers := NewSet[int]()
75 numbers.Add(1)
76 numbers.Add(2)
77 numbers.Add(3)
78 numbers.Add(2) // Duplicate - won't be added
79
80 fmt.Printf("Numbers set: %v\n", numbers.Items())
81 fmt.Printf("Contains 2: %t\n", numbers.Contains(2))
82 fmt.Printf("Contains 4: %t\n", numbers.Contains(4))
83
84 // String set example
85 fruits := NewSet[string]()
86 fruits.Add("apple")
87 fruits.Add("banana")
88 fruits.Add("cherry")
89
90 colors := NewSet[string]()
91 colors.Add("red")
92 colors.Add("green")
93 colors.Add("blue")
94
95 // Set operations
96 union := fruits.Union(colors)
97 intersection := fruits.Intersection(colors)
98
99 fmt.Printf("Union: %v\n", union.Items())
100 fmt.Printf("Intersection: %v\n", intersection.Items())
101 fmt.Printf("Intersection size: %d\n", intersection.Size())
102}
Why struct{}? Using struct{}{} as the value type takes zero bytes of memory. We only care about the keys (set membership), not the values.
Advanced Map Patterns
Pattern 1: Map as Frequency Counter
A fundamental pattern for text processing, analytics, and data aggregation:
1// run
2package main
3
4import (
5 "fmt"
6 "sort"
7 "strings"
8)
9
10func wordFrequency(text string) map[string]int {
11 words := strings.Fields(strings.ToLower(text))
12 freq := make(map[string]int, len(words))
13
14 for _, word := range words {
15 freq[word]++
16 }
17
18 return freq
19}
20
21func topN(freq map[string]int, n int) []string {
22 items := make([]string, 0, len(freq))
23 for word := range freq {
24 items = append(items, word)
25 }
26
27 sort.Slice(items, func(i, j int) bool {
28 return freq[items[i]] > freq[items[j]]
29 })
30
31 if len(items) > n {
32 items = items[:n]
33 }
34
35 return items
36}
37
38func main() {
39 text := "the quick brown fox jumps over the lazy dog the fox was quick and the dog was lazy"
40
41 freq := wordFrequency(text)
42 fmt.Println("Word frequencies:")
43 for word, count := range freq {
44 fmt.Printf("%s: %d\n", word, count)
45 }
46
47 fmt.Println("\nTop 5 words:")
48 top := topN(freq, 5)
49 for i, word := range top {
50 fmt.Printf("%d. %s (%d occurrences)\n", i+1, word, freq[word])
51 }
52}
Pattern 2: Map Caching Pattern
Memoization improves performance by caching expensive computations:
1// run
2package main
3
4import (
5 "fmt"
6 "sync"
7 "time"
8)
9
10type Cache struct {
11 data map[int]int
12 mu sync.RWMutex
13}
14
15func NewCache() *Cache {
16 return &Cache{
17 data: make(map[int]int),
18 }
19}
20
21func (c *Cache) Get(key int) (int, bool) {
22 c.mu.RLock()
23 defer c.mu.RUnlock()
24
25 value, exists := c.data[key]
26 return value, exists
27}
28
29func (c *Cache) Set(key, value int) {
30 c.mu.Lock()
31 defer c.mu.Unlock()
32
33 c.data[key] = value
34}
35
36// Fibonacci with caching
37func fibonacci(n int, cache *Cache) int {
38 if n <= 1 {
39 return n
40 }
41
42 if cached, exists := cache.Get(n); exists {
43 return cached
44 }
45
46 result := fibonacci(n-1, cache) + fibonacci(n-2, cache)
47 cache.Set(n, result)
48 return result
49}
50
51func main() {
52 cache := NewCache()
53
54 // Measure with cache
55 start := time.Now()
56 result := fibonacci(40, cache)
57 duration := time.Since(start)
58
59 fmt.Printf("Fibonacci(40) = %d\n", result)
60 fmt.Printf("Computed in: %v\n", duration)
61 fmt.Printf("Cache size: %d entries\n", len(cache.data))
62}
Pattern 3: Map for Configuration Management
Maps excel at managing application configuration:
1// run
2package main
3
4import (
5 "fmt"
6 "time"
7)
8
9type Config struct {
10 settings map[string]interface{}
11 defaults map[string]interface{}
12}
13
14func NewConfig() *Config {
15 return &Config{
16 settings: make(map[string]interface{}),
17 defaults: map[string]interface{}{
18 "timeout": 30 * time.Second,
19 "max_retries": 3,
20 "buffer_size": 1024,
21 "debug": false,
22 "server_addr": "localhost:8080",
23 },
24 }
25}
26
27func (c *Config) Set(key string, value interface{}) {
28 c.settings[key] = value
29}
30
31func (c *Config) Get(key string) interface{} {
32 // Check user settings first
33 if value, exists := c.settings[key]; exists {
34 return value
35 }
36
37 // Fall back to defaults
38 if value, exists := c.defaults[key]; exists {
39 return value
40 }
41
42 return nil
43}
44
45func (c *Config) GetString(key string) string {
46 if value := c.Get(key); value != nil {
47 if str, ok := value.(string); ok {
48 return str
49 }
50 }
51 return ""
52}
53
54func (c *Config) GetInt(key string) int {
55 if value := c.Get(key); value != nil {
56 if num, ok := value.(int); ok {
57 return num
58 }
59 }
60 return 0
61}
62
63func (c *Config) GetDuration(key string) time.Duration {
64 if value := c.Get(key); value != nil {
65 if duration, ok := value.(time.Duration); ok {
66 return duration
67 }
68 }
69 return 0
70}
71
72func (c *Config) GetBool(key string) bool {
73 if value := c.Get(key); value != nil {
74 if b, ok := value.(bool); ok {
75 return b
76 }
77 }
78 return false
79}
80
81func main() {
82 config := NewConfig()
83
84 // Use defaults
85 fmt.Printf("Default timeout: %v\n", config.GetDuration("timeout"))
86 fmt.Printf("Default retries: %d\n", config.GetInt("max_retries"))
87
88 // Override settings
89 config.Set("timeout", 60*time.Second)
90 config.Set("debug", true)
91
92 fmt.Printf("\nAfter overrides:\n")
93 fmt.Printf("Timeout: %v\n", config.GetDuration("timeout"))
94 fmt.Printf("Debug: %t\n", config.GetBool("debug"))
95 fmt.Printf("Server: %s\n", config.GetString("server_addr"))
96}
Pattern 4: Map-Based State Machine
Maps can elegantly represent state transitions:
1// run
2package main
3
4import "fmt"
5
6type State string
7type Event string
8
9const (
10 StatePending State = "pending"
11 StateActive State = "active"
12 StateSuspended State = "suspended"
13 StateClosed State = "closed"
14)
15
16const (
17 EventActivate Event = "activate"
18 EventSuspend Event = "suspend"
19 EventResume Event = "resume"
20 EventClose Event = "close"
21)
22
23type StateMachine struct {
24 current State
25 transitions map[State]map[Event]State
26}
27
28func NewStateMachine() *StateMachine {
29 return &StateMachine{
30 current: StatePending,
31 transitions: map[State]map[Event]State{
32 StatePending: {
33 EventActivate: StateActive,
34 EventClose: StateClosed,
35 },
36 StateActive: {
37 EventSuspend: StateSuspended,
38 EventClose: StateClosed,
39 },
40 StateSuspended: {
41 EventResume: StateActive,
42 EventClose: StateClosed,
43 },
44 StateClosed: {},
45 },
46 }
47}
48
49func (sm *StateMachine) Trigger(event Event) (bool, error) {
50 if validTransitions, exists := sm.transitions[sm.current]; exists {
51 if nextState, valid := validTransitions[event]; valid {
52 fmt.Printf("State transition: %s -> %s (event: %s)\n",
53 sm.current, nextState, event)
54 sm.current = nextState
55 return true, nil
56 }
57 }
58
59 return false, fmt.Errorf("invalid transition: %s in state %s",
60 event, sm.current)
61}
62
63func (sm *StateMachine) Current() State {
64 return sm.current
65}
66
67func main() {
68 sm := NewStateMachine()
69
70 fmt.Printf("Initial state: %s\n\n", sm.Current())
71
72 // Valid transitions
73 sm.Trigger(EventActivate)
74 sm.Trigger(EventSuspend)
75 sm.Trigger(EventResume)
76
77 // Invalid transition
78 _, err := sm.Trigger(EventActivate)
79 if err != nil {
80 fmt.Printf("Error: %v\n", err)
81 }
82
83 // Final transition
84 sm.Trigger(EventClose)
85 fmt.Printf("\nFinal state: %s\n", sm.Current())
86}
Pattern 5: Inverted Index
Essential for search functionality and text analysis:
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7)
8
9type Document struct {
10 ID int
11 Content string
12}
13
14type InvertedIndex struct {
15 index map[string][]int // word -> document IDs
16}
17
18func NewInvertedIndex() *InvertedIndex {
19 return &InvertedIndex{
20 index: make(map[string][]int),
21 }
22}
23
24func (idx *InvertedIndex) Add(doc Document) {
25 words := strings.Fields(strings.ToLower(doc.Content))
26
27 // Track unique words in this document
28 seen := make(map[string]bool)
29
30 for _, word := range words {
31 // Remove punctuation
32 word = strings.Trim(word, ".,!?;:")
33
34 if !seen[word] {
35 idx.index[word] = append(idx.index[word], doc.ID)
36 seen[word] = true
37 }
38 }
39}
40
41func (idx *InvertedIndex) Search(query string) []int {
42 query = strings.ToLower(query)
43 return idx.index[query]
44}
45
46func (idx *InvertedIndex) SearchMultiple(queries []string) []int {
47 if len(queries) == 0 {
48 return nil
49 }
50
51 // Start with documents matching first query
52 resultMap := make(map[int]int)
53 for _, docID := range idx.Search(queries[0]) {
54 resultMap[docID] = 1
55 }
56
57 // Intersect with documents matching other queries
58 for _, query := range queries[1:] {
59 for _, docID := range idx.Search(query) {
60 resultMap[docID]++
61 }
62 }
63
64 // Return documents matching all queries
65 var results []int
66 for docID, count := range resultMap {
67 if count == len(queries) {
68 results = append(results, docID)
69 }
70 }
71
72 return results
73}
74
75func main() {
76 index := NewInvertedIndex()
77
78 // Add documents
79 docs := []Document{
80 {1, "The quick brown fox jumps over the lazy dog"},
81 {2, "A quick brown dog runs fast"},
82 {3, "The lazy cat sleeps all day"},
83 {4, "A brown fox and a brown dog"},
84 }
85
86 for _, doc := range docs {
87 index.Add(doc)
88 }
89
90 // Single word search
91 fmt.Println("Documents containing 'brown':")
92 fmt.Println(index.Search("brown"))
93
94 fmt.Println("\nDocuments containing 'lazy':")
95 fmt.Println(index.Search("lazy"))
96
97 // Multi-word search
98 fmt.Println("\nDocuments containing both 'brown' and 'dog':")
99 fmt.Println(index.SearchMultiple([]string{"brown", "dog"}))
100}
Common Patterns and Pitfalls
Common Pitfall 1: Nil Map Access
1// run
2package main
3
4import "fmt"
5
6func main() {
7 var m map[string]int // nil map
8
9 // ✅ Reading is safe
10 value := m["key"]
11 fmt.Printf("Value: %d\n", value) // Returns zero value
12
13 // ✅ Checking existence is safe
14 _, exists := m["key"]
15 fmt.Printf("Exists: %t\n", exists)
16
17 // ✅ Length is safe
18 fmt.Printf("Length: %d\n", len(m))
19
20 // ✅ Ranging over nil map is safe (does nothing)
21 for k, v := range m {
22 fmt.Printf("%s: %d\n", k, v)
23 }
24
25 // ❌ Writing panics!
26 // m["key"] = 1 // panic: assignment to entry in nil map
27
28 // ✅ Initialize before writing
29 m = make(map[string]int)
30 m["key"] = 1
31 fmt.Printf("After initialization: %v\n", m)
32}
Common Pitfall 2: Concurrent Access
1// run
2package main
3
4import (
5 "fmt"
6 "sync"
7)
8
9func main() {
10 // ❌ UNSAFE: Concurrent writes without synchronization
11 unsafeDemo := func() {
12 m := make(map[int]int)
13 var wg sync.WaitGroup
14
15 // This would cause a "concurrent map writes" panic
16 // Commented out for safety
17 /*
18 for i := 0; i < 10; i++ {
19 wg.Add(1)
20 go func(id int) {
21 defer wg.Done()
22 m[id] = id * 2 // ❌ Unsafe!
23 }(i)
24 }
25 wg.Wait()
26 */
27 fmt.Println("Unsafe concurrent access (commented out for safety)")
28 }
29
30 // ✅ SAFE: Use sync.Map for simple cases
31 safeWithSyncMap := func() {
32 var m sync.Map
33 var wg sync.WaitGroup
34
35 for i := 0; i < 10; i++ {
36 wg.Add(1)
37 go func(id int) {
38 defer wg.Done()
39 m.Store(id, id*2) // ✅ Safe!
40 }(i)
41 }
42 wg.Wait()
43
44 fmt.Println("Safe with sync.Map:")
45 m.Range(func(key, value interface{}) bool {
46 fmt.Printf("%d: %d\n", key, value)
47 return true
48 })
49 }
50
51 // ✅ SAFE: Use mutex for complex operations
52 safeWithMutex := func() {
53 type SafeMap struct {
54 mu sync.RWMutex
55 data map[int]int
56 }
57
58 sm := &SafeMap{
59 data: make(map[int]int),
60 }
61
62 var wg sync.WaitGroup
63 for i := 0; i < 10; i++ {
64 wg.Add(1)
65 go func(id int) {
66 defer wg.Done()
67 sm.mu.Lock()
68 sm.data[id] = id * 2 // ✅ Safe!
69 sm.mu.Unlock()
70 }(i)
71 }
72 wg.Wait()
73
74 fmt.Println("\nSafe with mutex:")
75 sm.mu.RLock()
76 for k, v := range sm.data {
77 fmt.Printf("%d: %d\n", k, v)
78 }
79 sm.mu.RUnlock()
80 }
81
82 unsafeDemo()
83 safeWithSyncMap()
84 safeWithMutex()
85}
Common Pitfall 3: Map Modification During Iteration
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // ❌ PROBLEMATIC: Deleting during iteration
8 unsafeDelete := func() {
9 m := map[string]int{
10 "a": 1,
11 "b": 2,
12 "c": 3,
13 "d": 4,
14 }
15
16 fmt.Println("Deleting during iteration (may skip elements):")
17 for k := range m {
18 if k == "b" || k == "c" {
19 delete(m, k) // May cause unexpected behavior
20 }
21 }
22 fmt.Printf("Result: %v\n", m)
23 }
24
25 // ✅ SAFE: Collect keys first, then delete
26 safeDelete := func() {
27 m := map[string]int{
28 "a": 1,
29 "b": 2,
30 "c": 3,
31 "d": 4,
32 }
33
34 // Collect keys to delete
35 toDelete := []string{}
36 for k := range m {
37 if k == "b" || k == "c" {
38 toDelete = append(toDelete, k)
39 }
40 }
41
42 // Delete after iteration
43 for _, k := range toDelete {
44 delete(m, k)
45 }
46
47 fmt.Println("\nSafe deletion:")
48 fmt.Printf("Result: %v\n", m)
49 }
50
51 unsafeDelete()
52 safeDelete()
53}
Common Pitfall 4: Map Equality Comparison
1// run
2package main
3
4import "fmt"
5
6func mapsEqual[K, V comparable](m1, m2 map[K]V) bool {
7 if len(m1) != len(m2) {
8 return false
9 }
10
11 for k, v1 := range m1 {
12 v2, exists := m2[k]
13 if !exists || v1 != v2 {
14 return false
15 }
16 }
17
18 return true
19}
20
21func main() {
22 m1 := map[string]int{"a": 1, "b": 2}
23 m2 := map[string]int{"a": 1, "b": 2}
24 m3 := map[string]int{"a": 1, "b": 3}
25
26 // ❌ Cannot compare maps directly
27 // fmt.Println(m1 == m2) // Compile error!
28
29 // ✅ Use custom comparison function
30 fmt.Printf("m1 == m2: %t\n", mapsEqual(m1, m2))
31 fmt.Printf("m1 == m3: %t\n", mapsEqual(m1, m3))
32}
Integration and Mastery - Building Real Applications
Example 1: Multi-Level Caching System
1// run
2package main
3
4import (
5 "fmt"
6 "sync"
7 "time"
8)
9
10type CacheItem struct {
11 Value interface{}
12 ExpiresAt time.Time
13 AccessCount int
14 LastAccess time.Time
15}
16
17type MultiLevelCache struct {
18 // L1: Fast, small cache
19 l1Cache map[string]*CacheItem
20 l1Size int
21 l1Mu sync.RWMutex
22
23 // L2: Slower, larger cache
24 l2Cache map[string]*CacheItem
25 l2Size int
26 l2Mu sync.RWMutex
27
28 // Statistics
29 hits int64
30 misses int64
31}
32
33func NewMultiLevelCache(l1Size, l2Size int) *MultiLevelCache {
34 return &MultiLevelCache{
35 l1Cache: make(map[string]*CacheItem, l1Size),
36 l2Cache: make(map[string]*CacheItem, l2Size),
37 l1Size: l1Size,
38 l2Size: l2Size,
39 }
40}
41
42func (c *MultiLevelCache) Get(key string) (interface{}, bool) {
43 // Try L1 cache first
44 c.l1Mu.RLock()
45 if item, exists := c.l1Cache[key]; exists {
46 if time.Now().Before(item.ExpiresAt) {
47 item.AccessCount++
48 item.LastAccess = time.Now()
49 c.l1Mu.RUnlock()
50 c.hits++
51 return item.Value, true
52 }
53 // Item expired, remove it
54 c.l1Mu.RUnlock()
55 c.l1Mu.Lock()
56 delete(c.l1Cache, key)
57 c.l1Mu.Unlock()
58 } else {
59 c.l1Mu.RUnlock()
60 }
61
62 // Try L2 cache
63 c.l2Mu.RLock()
64 if item, exists := c.l2Cache[key]; exists {
65 if time.Now().Before(item.ExpiresAt) {
66 item.AccessCount++
67 item.LastAccess = time.Now()
68 value := item.Value
69 c.l2Mu.RUnlock()
70
71 // Promote to L1 if there's space
72 c.promoteToL1(key, item)
73 c.hits++
74 return value, true
75 }
76 // Item expired
77 c.l2Mu.RUnlock()
78 c.l2Mu.Lock()
79 delete(c.l2Cache, key)
80 c.l2Mu.Unlock()
81 } else {
82 c.l2Mu.RUnlock()
83 }
84
85 c.misses++
86 return nil, false
87}
88
89func (c *MultiLevelCache) Set(key string, value interface{}, ttl time.Duration) {
90 expiresAt := time.Now().Add(ttl)
91 item := &CacheItem{
92 Value: value,
93 ExpiresAt: expiresAt,
94 AccessCount: 1,
95 LastAccess: time.Now(),
96 }
97
98 // Try to store in L1 first
99 c.l1Mu.Lock()
100 if len(c.l1Cache) < c.l1Size {
101 c.l1Cache[key] = item
102 c.l1Mu.Unlock()
103 return
104 }
105 c.l1Mu.Unlock()
106
107 // Store in L2
108 c.l2Mu.Lock()
109 if len(c.l2Cache) >= c.l2Size {
110 c.evictFromL2()
111 }
112 c.l2Cache[key] = item
113 c.l2Mu.Unlock()
114}
115
116func (c *MultiLevelCache) promoteToL1(key string, item *CacheItem) {
117 c.l1Mu.Lock()
118 defer c.l1Mu.Unlock()
119
120 // Evict if necessary
121 if len(c.l1Cache) >= c.l1Size {
122 c.evictFromL1()
123 }
124
125 c.l1Cache[key] = item
126}
127
128func (c *MultiLevelCache) evictFromL1() {
129 // Evict least recently used item
130 var oldestKey string
131 var oldestTime time.Time
132
133 for key, item := range c.l1Cache {
134 if oldestKey == "" || item.LastAccess.Before(oldestTime) {
135 oldestKey = key
136 oldestTime = item.LastAccess
137 }
138 }
139
140 if oldestKey != "" {
141 delete(c.l1Cache, oldestKey)
142 }
143}
144
145func (c *MultiLevelCache) evictFromL2() {
146 // Evict least recently used item
147 var oldestKey string
148 var oldestTime time.Time
149
150 for key, item := range c.l2Cache {
151 if oldestKey == "" || item.LastAccess.Before(oldestTime) {
152 oldestKey = key
153 oldestTime = item.LastAccess
154 }
155 }
156
157 if oldestKey != "" {
158 delete(c.l2Cache, oldestKey)
159 }
160}
161
162func (c *MultiLevelCache) Stats() (int64, int64, float64) {
163 total := c.hits + c.misses
164 hitRate := 0.0
165 if total > 0 {
166 hitRate = float64(c.hits) / float64(total) * 100
167 }
168 return c.hits, c.misses, hitRate
169}
170
171func main() {
172 cache := NewMultiLevelCache(10, 100) // L1: 10 items, L2: 100 items
173
174 fmt.Println("=== Multi-Level Cache Demo ===")
175
176 // Test cache operations
177 keys := []string{"user:1", "user:2", "config:app", "session:abc123", "product:12345"}
178
179 // Set some values
180 for _, key := range keys {
181 cache.Set(key, "value-for-"+key, time.Minute*5)
182 fmt.Printf("Cached: %s\n", key)
183 }
184
185 // Retrieve values
186 for _, key := range keys {
187 if value, found := cache.Get(key); found {
188 fmt.Printf("Retrieved: %s = %v\n", key, value)
189 } else {
190 fmt.Printf("Not found: %s\n", key)
191 }
192 }
193
194 // Add more items to trigger evictions
195 for i := 0; i < 150; i++ {
196 cache.Set(fmt.Sprintf("temp:%d", i), i, time.Minute)
197 }
198
199 // Check cache stats
200 hits, misses, hitRate := cache.Stats()
201 fmt.Printf("\nCache Statistics:\n")
202 fmt.Printf("Hits: %d\n", hits)
203 fmt.Printf("Misses: %d\n", misses)
204 fmt.Printf("Hit Rate: %.2f%%\n", hitRate)
205}
Example 2: Real-Time Analytics System
1// run
2package main
3
4import (
5 "fmt"
6 "sync"
7 "time"
8)
9
10type Event struct {
11 Type string
12 Timestamp time.Time
13 UserID string
14 Value float64
15 Metadata map[string]interface{}
16}
17
18type Analytics struct {
19 // Event counters by type
20 eventCounts map[string]int64
21
22 // User activity tracking
23 userActivity map[string]time.Time
24
25 // Time-based aggregations
26 hourlyStats map[string]map[string]int64
27
28 // Value aggregations
29 valueStats map[string]ValueAggregate
30
31 mu sync.RWMutex
32}
33
34type ValueAggregate struct {
35 Count int64
36 Sum float64
37 Min float64
38 Max float64
39}
40
41func NewAnalytics() *Analytics {
42 return &Analytics{
43 eventCounts: make(map[string]int64),
44 userActivity: make(map[string]time.Time),
45 hourlyStats: make(map[string]map[string]int64),
46 valueStats: make(map[string]ValueAggregate),
47 }
48}
49
50func (a *Analytics) TrackEvent(event Event) {
51 a.mu.Lock()
52 defer a.mu.Unlock()
53
54 // Count event types
55 a.eventCounts[event.Type]++
56
57 // Track user activity
58 a.userActivity[event.UserID] = event.Timestamp
59
60 // Hourly statistics
61 hourKey := event.Timestamp.Format("2006-01-02-15")
62 if a.hourlyStats[hourKey] == nil {
63 a.hourlyStats[hourKey] = make(map[string]int64)
64 }
65 a.hourlyStats[hourKey][event.Type]++
66
67 // Value aggregations
68 if event.Value > 0 {
69 agg := a.valueStats[event.Type]
70 agg.Count++
71 agg.Sum += event.Value
72
73 if agg.Count == 1 || event.Value < agg.Min {
74 agg.Min = event.Value
75 }
76 if agg.Count == 1 || event.Value > agg.Max {
77 agg.Max = event.Value
78 }
79
80 a.valueStats[event.Type] = agg
81 }
82}
83
84func (a *Analytics) GetEventCounts() map[string]int64 {
85 a.mu.RLock()
86 defer a.mu.RUnlock()
87
88 result := make(map[string]int64)
89 for k, v := range a.eventCounts {
90 result[k] = v
91 }
92 return result
93}
94
95func (a *Analytics) GetActiveUsers(since time.Time) []string {
96 a.mu.RLock()
97 defer a.mu.RUnlock()
98
99 var activeUsers []string
100 for userID, lastSeen := range a.userActivity {
101 if lastSeen.After(since) {
102 activeUsers = append(activeUsers, userID)
103 }
104 }
105 return activeUsers
106}
107
108func (a *Analytics) GetHourlyStats() map[string]map[string]int64 {
109 a.mu.RLock()
110 defer a.mu.RUnlock()
111
112 result := make(map[string]map[string]int64)
113 for hour, events := range a.hourlyStats {
114 result[hour] = make(map[string]int64)
115 for eventType, count := range events {
116 result[hour][eventType] = count
117 }
118 }
119 return result
120}
121
122func (a *Analytics) GetValueStats(eventType string) (ValueAggregate, bool) {
123 a.mu.RLock()
124 defer a.mu.RUnlock()
125
126 agg, exists := a.valueStats[eventType]
127 return agg, exists
128}
129
130func (a *Analytics) GenerateReport() {
131 a.mu.RLock()
132 defer a.mu.RUnlock()
133
134 fmt.Println("=== Analytics Report ===")
135
136 // Event type summary
137 fmt.Println("\nEvent Summary:")
138 for eventType, count := range a.eventCounts {
139 fmt.Printf(" %s: %d events\n", eventType, count)
140 }
141
142 // Active users
143 oneHourAgo := time.Now().Add(-time.Hour)
144 activeUsers := 0
145 for _, lastSeen := range a.userActivity {
146 if lastSeen.After(oneHourAgo) {
147 activeUsers++
148 }
149 }
150 fmt.Printf("\nActive Users (last hour): %d\n", activeUsers)
151
152 // Value statistics
153 fmt.Println("\nValue Statistics:")
154 for eventType, agg := range a.valueStats {
155 fmt.Printf(" %s:\n", eventType)
156 fmt.Printf(" Count: %d\n", agg.Count)
157 fmt.Printf(" Average: %.2f\n", agg.Sum/float64(agg.Count))
158 fmt.Printf(" Range: %.2f - %.2f\n", agg.Min, agg.Max)
159 }
160
161 // Recent hourly activity
162 fmt.Println("\nRecent Hourly Activity:")
163 now := time.Now()
164 for i := 0; i < 3; i++ {
165 hourKey := now.Add(time.Duration(-i) * time.Hour).Format("2006-01-02-15")
166 if hourStats, exists := a.hourlyStats[hourKey]; exists {
167 total := 0
168 for _, count := range hourStats {
169 total += int(count)
170 }
171 fmt.Printf(" %s: %d events\n", hourKey, total)
172 }
173 }
174}
175
176func main() {
177 analytics := NewAnalytics()
178
179 fmt.Println("=== Real-Time Analytics System ===")
180
181 // Simulate some events
182 events := []Event{
183 {"login", time.Now().Add(-2 * time.Hour), "user1", 0, map[string]interface{}{"ip": "192.168.1.1"}},
184 {"page_view", time.Now().Add(-90 * time.Minute), "user1", 0, map[string]interface{}{"page": "/home"}},
185 {"purchase", time.Now().Add(-85 * time.Minute), "user1", 29.99, map[string]interface{}{"product": "book"}},
186 {"login", time.Now().Add(-60 * time.Minute), "user2", 0, map[string]interface{}{"ip": "192.168.1.2"}},
187 {"page_view", time.Now().Add(-45 * time.Minute), "user2", 0, map[string]interface{}{"page": "/products"}},
188 {"purchase", time.Now().Add(-30 * time.Minute), "user2", 49.99, map[string]interface{}{"product": "electronics"}},
189 {"login", time.Now().Add(-15 * time.Minute), "user3", 0, map[string]interface{}{"ip": "192.168.1.3"}},
190 {"page_view", time.Now().Add(-10 * time.Minute), "user3", 0, map[string]interface{}{"page": "/about"}},
191 {"click", time.Now().Add(-5 * time.Minute), "user3", 0, map[string]interface{}{"element": "button"}},
192 }
193
194 // Track events
195 for _, event := range events {
196 analytics.TrackEvent(event)
197 }
198
199 // Generate report
200 analytics.GenerateReport()
201
202 // Additional queries
203 fmt.Println("\n=== Additional Queries ===")
204
205 eventCounts := analytics.GetEventCounts()
206 fmt.Printf("Total login events: %d\n", eventCounts["login"])
207 fmt.Printf("Total purchase events: %d\n", eventCounts["purchase"])
208
209 activeUsers := analytics.GetActiveUsers(time.Now().Add(-time.Hour))
210 fmt.Printf("Active users: %v\n", activeUsers)
211
212 if purchaseStats, exists := analytics.GetValueStats("purchase"); exists {
213 fmt.Printf("Purchase statistics: avg=$%.2f, range=$%.2f-$%.2f\n",
214 purchaseStats.Sum/float64(purchaseStats.Count),
215 purchaseStats.Min,
216 purchaseStats.Max)
217 }
218}
Performance Considerations
Map Growth and Memory
Understanding how maps grow is crucial for performance optimization:
1// run
2package main
3
4import (
5 "fmt"
6 "runtime"
7 "time"
8)
9
10func mapGrowthDemo() {
11 fmt.Println("=== Map Growth Analysis ===")
12
13 // Pre-allocated map
14 prealloc := make(map[int]string, 10000)
15 start := time.Now()
16 for i := 0; i < 10000; i++ {
17 prealloc[i] = fmt.Sprintf("value%d", i)
18 }
19 preallocTime := time.Since(start)
20
21 // Growing map
22 growing := make(map[int]string)
23 start = time.Now()
24 for i := 0; i < 10000; i++ {
25 growing[i] = fmt.Sprintf("value%d", i)
26 }
27 growingTime := time.Since(start)
28
29 fmt.Printf("Pre-allocated: %v\n", preallocTime)
30 fmt.Printf("Growing: %v\n", growingTime)
31 fmt.Printf("Speedup: %.2fx\n", float64(growingTime.Nanoseconds())/float64(preallocTime.Nanoseconds()))
32
33 // Memory usage
34 runtime.GC()
35 var m1, m2 runtime.MemStats
36 runtime.ReadMemStats(&m1)
37
38 prealloc = make(map[int]string, 10000)
39 for i := 0; i < 10000; i++ {
40 prealloc[i] = fmt.Sprintf("value%d", i)
41 }
42
43 runtime.ReadMemStats(&m2)
44 fmt.Printf("Memory for pre-allocated map: %d KB\n", (m2.Alloc-m1.Alloc)/1024)
45}
46
47func mapPerformanceComparison() {
48 fmt.Println("\n=== Map Performance Comparison ===")
49
50 size := 100000
51 testMap := make(map[string]int, size)
52
53 // Initialize test data
54 for i := 0; i < size; i++ {
55 key := fmt.Sprintf("key_%d", i)
56 testMap[key] = i
57 }
58
59 // Test access performance
60 start := time.Now()
61 sum := 0
62 for i := 0; i < size; i++ {
63 key := fmt.Sprintf("key_%d", i)
64 sum += testMap[key]
65 }
66 accessTime := time.Since(start)
67 fmt.Printf("Sequential access: %v (sum: %d)\n", accessTime, sum)
68
69 // Test range performance
70 start = time.Now()
71 count := 0
72 for _, value := range testMap {
73 count += value
74 }
75 rangeTime := time.Since(start)
76 fmt.Printf("Range iteration: %v (sum: %d)\n", rangeTime, count)
77
78 // Test deletion performance
79 start = time.Now()
80 for i := 0; i < size/2; i++ {
81 key := fmt.Sprintf("key_%d", i)
82 delete(testMap, key)
83 }
84 deleteTime := time.Since(start)
85 fmt.Printf("Half deletion: %v\n", deleteTime)
86 fmt.Printf("Remaining items: %d\n", len(testMap))
87}
88
89func main() {
90 mapGrowthDemo()
91 mapPerformanceComparison()
92}
Key Type Selection
Different key types have different performance characteristics:
1// run
2package main
3
4import (
5 "fmt"
6 "time"
7)
8
9type UserKey int
10type ProductKey string
11
12type ComplexKey struct {
13 Type string
14 ID int
15 Region string
16}
17
18func keyTypeComparison() {
19 fmt.Println("=== Key Type Performance ===")
20
21 iterations := 100000
22
23 // Integer keys
24 intMap := make(map[int]string, iterations)
25 start := time.Now()
26 for i := 0; i < iterations; i++ {
27 intMap[i] = fmt.Sprintf("value%d", i)
28 }
29 intTime := time.Since(start)
30
31 // String keys
32 strMap := make(map[string]string, iterations)
33 start = time.Now()
34 for i := 0; i < iterations; i++ {
35 key := fmt.Sprintf("key_%d", i)
36 strMap[key] = fmt.Sprintf("value%d", i)
37 }
38 strTime := time.Since(start)
39
40 // Struct keys
41 structMap := make(map[ComplexKey]string, iterations/10)
42 start = time.Now()
43 for i := 0; i < iterations/10; i++ {
44 key := ComplexKey{Type: "user", ID: i, Region: "us-east"}
45 structMap[key] = fmt.Sprintf("value%d", i)
46 }
47 structTime := time.Since(start)
48
49 fmt.Printf("Integer keys: %v\n", intTime)
50 fmt.Printf("String keys: %v\n", strTime)
51 fmt.Printf("Struct keys: %v\n", structTime)
52
53 // Performance comparison
54 fmt.Printf("\nPerformance ratios:\n")
55 fmt.Printf("String vs Int: %.2fx slower\n", float64(strTime.Nanoseconds())/float64(intTime.Nanoseconds()))
56 fmt.Printf("Struct vs Int: %.2fx slower\n", float64(structTime.Nanoseconds())/float64(intTime.Nanoseconds()))
57}
58
59func main() {
60 keyTypeComparison()
61}
Map vs Slice Performance Trade-offs
1// run
2package main
3
4import (
5 "fmt"
6 "time"
7)
8
9type Item struct {
10 ID int
11 Name string
12}
13
14func main() {
15 fmt.Println("=== Map vs Slice Performance ===")
16
17 size := 10000
18
19 // Build test data
20 items := make([]Item, size)
21 for i := 0; i < size; i++ {
22 items[i] = Item{ID: i, Name: fmt.Sprintf("Item %d", i)}
23 }
24
25 // Map lookup performance
26 itemMap := make(map[int]Item, size)
27 for _, item := range items {
28 itemMap[item.ID] = item
29 }
30
31 start := time.Now()
32 for i := 0; i < 1000; i++ {
33 _ = itemMap[i*10]
34 }
35 mapTime := time.Since(start)
36
37 // Slice linear search performance
38 start = time.Now()
39 for i := 0; i < 1000; i++ {
40 target := i * 10
41 for _, item := range items {
42 if item.ID == target {
43 _ = item
44 break
45 }
46 }
47 }
48 sliceTime := time.Since(start)
49
50 fmt.Printf("Map lookups: %v\n", mapTime)
51 fmt.Printf("Slice lookups: %v\n", sliceTime)
52 fmt.Printf("Map is %.1fx faster\n",
53 float64(sliceTime.Nanoseconds())/float64(mapTime.Nanoseconds()))
54}
Practice Exercises
Exercise 1: Word Frequency Counter
🎯 Learning Objectives: Master map iteration, key-value operations, and frequency counting algorithms.
🌍 Real-World Context: Word frequency analysis is fundamental to text processing, search engines, and natural language processing. Search engines use it for indexing, social media platforms use it for trending topics, and content analysis tools use it for keyword extraction.
⭐ Difficulty: Beginner | ⏱️ Time Estimate: 15 minutes
Write a program that counts the frequency of each word in a given text.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7)
8
9func wordFrequency(text string) map[string]int {
10 words := strings.Fields(strings.ToLower(text))
11 freq := make(map[string]int)
12
13 for _, word := range words {
14 freq[word]++
15 }
16
17 return freq
18}
19
20func main() {
21 text := "Go is an open source programming language that makes it easy to build simple reliable and efficient software"
22
23 freq := wordFrequency(text)
24
25 fmt.Println("Word frequency:")
26 for word, count := range freq {
27 fmt.Printf("%s: %d\n", word, count)
28 }
29}
Exercise 2: Anagram Detector
🎯 Learning Objectives: Practice map-based character counting and string manipulation algorithms.
🌍 Real-World Context: Anagram detection is used in word games, cryptography, and text analysis. Spell checkers use it to suggest corrections, security systems use it in password strength validation, and puzzle games use it extensively.
⭐ Difficulty: Beginner | ⏱️ Time Estimate: 12 minutes
Write a function that determines if two strings are anagrams of each other.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "sort"
7)
8
9// Method 1: Using character frequency maps
10func areAnagramsMap(s1, s2 string) bool {
11 if len(s1) != len(s2) {
12 return false
13 }
14
15 freq := make(map[rune]int)
16
17 // Count characters in first string
18 for _, char := range s1 {
19 freq[char]++
20 }
21
22 // Subtract counts using second string
23 for _, char := range s2 {
24 freq[char]--
25 if freq[char] < 0 {
26 return false
27 }
28 }
29
30 return true
31}
32
33// Method 2: Using sorting
34func areAnagramsSort(s1, s2 string) bool {
35 if len(s1) != len(s2) {
36 return false
37 }
38
39 runes1 := []rune(s1)
40 runes2 := []rune(s2)
41
42 sort.Slice(runes1, func(i, j int) bool {
43 return runes1[i] < runes1[j]
44 })
45
46 sort.Slice(runes2, func(i, j int) bool {
47 return runes2[i] < runes2[j]
48 })
49
50 return string(runes1) == string(runes2)
51}
52
53func main() {
54 tests := []struct {
55 s1, s2 string
56 expected bool
57 }{
58 {"listen", "silent", true},
59 {"hello", "world", false},
60 {"triangle", "integral", true},
61 {"apple", "papel", true},
62 {"test", "sett", true},
63 }
64
65 fmt.Println("Anagram detection:")
66 for _, test := range tests {
67 result1 := areAnagramsMap(test.s1, test.s2)
68 result2 := areAnagramsSort(test.s1, test.s2)
69
70 fmt.Printf("%s + %s: map=%t, sort=%t - expected: %t\n",
71 test.s1, test.s2, result1, result2, test.expected)
72 }
73}
Exercise 3: Map Merging
🎯 Learning Objectives: Implement map combination operations and understand map copying semantics.
🌍 Real-World Context: Map merging is essential for configuration management, data aggregation, and API response handling. Configuration systems merge default and user settings, analytics systems merge data from multiple sources, and database queries often require result merging.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 18 minutes
Write a function that merges two maps, with the second map's values taking precedence.
Solution
1// run
2package main
3
4import "fmt"
5
6func mergeMaps[K comparable, V any](map1, map2 map[K]V) map[K]V {
7 result := make(map[K]V, len(map1)+len(map2))
8
9 // Copy first map
10 for key, value := range map1 {
11 result[key] = value
12 }
13
14 // Copy second map (overwriting conflicts)
15 for key, value := range map2 {
16 result[key] = value
17 }
18
19 return result
20}
21
22func mergeMapsAdd[K comparable](map1, map2 map[K]int) map[K]int {
23 result := make(map[K]int, len(map1)+len(map2))
24
25 // Copy first map
26 for key, value := range map1 {
27 result[key] = value
28 }
29
30 // Add values from second map
31 for key, value := range map2 {
32 result[key] += value
33 }
34
35 return result
36}
37
38func main() {
39 // Test basic merging
40 defaults := map[string]int{
41 "timeout": 30,
42 "retries": 3,
43 "buffer": 1024,
44 }
45
46 userConfig := map[string]int{
47 "timeout": 60, // Override default
48 "threads": 8, // New value
49 }
50
51 merged := mergeMaps(defaults, userConfig)
52 fmt.Println("Basic merge:")
53 for key, value := range merged {
54 fmt.Printf(" %s: %d\n", key, value)
55 }
56
57 // Test addition merging
58 sales1 := map[string]int{
59 "jan": 1000,
60 "feb": 1200,
61 "mar": 1100,
62 }
63
64 sales2 := map[string]int{
65 "mar": 200, // Additional for March
66 "apr": 1300,
67 "may": 1400,
68 }
69
70 totalSales := mergeMapsAdd(sales1, sales2)
71 fmt.Println("\nSales aggregation:")
72 for month, total := range totalSales {
73 fmt.Printf(" %s: %d\n", month, total)
74 }
75}
Exercise 4: Cache Implementation
🎯 Learning Objectives: Build a simple cache system using maps and understand caching patterns.
🌍 Real-World Context: Caching is fundamental to performance optimization. Web applications cache database results, file systems cache disk access, and APIs cache external service responses. Proper caching can reduce response times by orders of magnitude.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 25 minutes
Implement a simple LRU cache using maps.
Solution
1// run
2package main
3
4import (
5 "container/list"
6 "fmt"
7 "sync"
8 "time"
9)
10
11type CacheItem struct {
12 key string
13 value interface{}
14}
15
16type LRUCache struct {
17 capacity int
18 cache map[string]*list.Element
19 list *list.List
20 mu sync.RWMutex
21}
22
23func NewLRUCache(capacity int) *LRUCache {
24 return &LRUCache{
25 capacity: capacity,
26 cache: make(map[string]*list.Element),
27 list: list.New(),
28 }
29}
30
31func (c *LRUCache) Get(key string) (interface{}, bool) {
32 c.mu.Lock()
33 defer c.mu.Unlock()
34
35 if element, exists := c.cache[key]; exists {
36 // Move to front
37 c.list.MoveToFront(element)
38 return element.Value.(*CacheItem).value, true
39 }
40
41 return nil, false
42}
43
44func (c *LRUCache) Put(key string, value interface{}) {
45 c.mu.Lock()
46 defer c.mu.Unlock()
47
48 if element, exists := c.cache[key]; exists {
49 // Update existing item
50 c.list.MoveToFront(element)
51 element.Value.(*CacheItem).value = value
52 return
53 }
54
55 // Add new item
56 item := &CacheItem{key: key, value: value}
57 element := c.list.PushFront(item)
58 c.cache[key] = element
59
60 // Remove least recently used if at capacity
61 if c.list.Len() > c.capacity {
62 last := c.list.Back()
63 if last != nil {
64 c.list.Remove(last)
65 delete(c.cache, last.Value.(*CacheItem).key)
66 }
67 }
68}
69
70func (c *LRUCache) Size() int {
71 c.mu.RLock()
72 defer c.mu.RUnlock()
73 return c.list.Len()
74}
75
76func (c *LRUCache) Clear() {
77 c.mu.Lock()
78 defer c.mu.Unlock()
79
80 c.cache = make(map[string]*list.Element)
81 c.list = list.New()
82}
83
84func main() {
85 cache := NewLRUCache(3) // Capacity of 3 items
86
87 fmt.Println("=== LRU Cache Demo ===")
88
89 // Add items
90 cache.Put("a", "Value A")
91 cache.Put("b", "Value B")
92 cache.Put("c", "Value C")
93
94 fmt.Printf("Cache size after 3 items: %d\n", cache.Size())
95
96 // Access item 'a'
97 if value, found := cache.Get("a"); found {
98 fmt.Printf("Found 'a': %v\n", value)
99 }
100
101 // Add item 'd' (should evict 'b')
102 cache.Put("d", "Value D")
103 fmt.Printf("Cache size after adding 'd': %d\n", cache.Size())
104
105 // Check what's in cache
106 items := []string{"a", "b", "c", "d"}
107 for _, key := range items {
108 if value, found := cache.Get(key); found {
109 fmt.Printf("Found '%s': %v\n", key, value)
110 } else {
111 fmt.Printf("Not found '%s'\n", key)
112 }
113 }
114
115 // Demonstrate cache in concurrent scenario
116 fmt.Println("\n=== Concurrent Access Demo ===")
117 var wg sync.WaitGroup
118
119 // Multiple goroutines accessing cache
120 for i := 0; i < 5; i++ {
121 wg.Add(1)
122 go func(id int) {
123 defer wg.Done()
124 key := fmt.Sprintf("key_%d", id)
125 value := fmt.Sprintf("value_%d", id)
126
127 cache.Put(key, value)
128 time.Sleep(time.Millisecond * 10)
129
130 if retrieved, found := cache.Get(key); found {
131 fmt.Printf("Goroutine %d: retrieved %v\n", id, retrieved)
132 }
133 }(i)
134 }
135
136 wg.Wait()
137 fmt.Printf("Final cache size: %d\n", cache.Size())
138}
Exercise 5: Grouping and Aggregation
🎯 Learning Objectives: Master map-based data grouping and aggregation patterns for data analysis.
🌍 Real-World Context: Data grouping and aggregation are essential for analytics, reporting, and business intelligence. E-commerce platforms group sales by category and time, social media platforms group posts by user and topic, and financial applications group transactions by type and date.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 20 minutes
Create a system that groups data by multiple criteria and performs aggregations.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7 "time"
8)
9
10type Transaction struct {
11 ID string
12 Amount float64
13 Category string
14 Date time.Time
15 UserID string
16 Payment string
17}
18
19type TransactionStats struct {
20 Count int
21 Total float64
22 Average float64
23 Min float64
24 Max float64
25}
26
27func analyzeTransactions(transactions []Transaction) {
28 // Group by category
29 byCategory := make(map[string][]Transaction)
30 for _, tx := range transactions {
31 byCategory[tx.Category] = append(byCategory[tx.Category], tx)
32 }
33
34 // Group by user
35 byUser := make(map[string][]Transaction)
36 for _, tx := range transactions {
37 byUser[tx.UserID] = append(byUser[tx.UserID], tx)
38 }
39
40 // Group by payment method
41 byPayment := make(map[string][]Transaction)
42 for _, tx := range transactions {
43 byPayment[tx.Payment] = append(byPayment[tx.Payment], tx)
44 }
45
46 // Group by month
47 byMonth := make(map[string][]Transaction)
48 for _, tx := range transactions {
49 month := tx.Date.Format("2006-01")
50 byMonth[month] = append(byMonth[month], tx)
51 }
52
53 fmt.Println("=== Transaction Analysis ===")
54
55 // Category summary
56 fmt.Println("\nBy Category:")
57 for category, txs := range byCategory {
58 stats := calculateStats(txs)
59 fmt.Printf("%s: %d transactions, $%.2f total, avg: $%.2f\n",
60 category, stats.Count, stats.Total, stats.Average)
61 }
62
63 // User summary
64 fmt.Println("\nBy User:")
65 for userID, txs := range byUser {
66 stats := calculateStats(txs)
67 fmt.Printf("%s: %d transactions, $%.2f total, avg: $%.2f\n",
68 userID, stats.Count, stats.Total, stats.Average)
69 }
70
71 // Payment method summary
72 fmt.Println("\nBy Payment Method:")
73 for payment, txs := range byPayment {
74 stats := calculateStats(txs)
75 fmt.Printf("%s: %d transactions, $%.2f total, avg: $%.2f\n",
76 payment, stats.Count, stats.Total, stats.Average)
77 }
78
79 // Monthly trends
80 fmt.Println("\nBy Month:")
81 for month, txs := range byMonth {
82 stats := calculateStats(txs)
83 fmt.Printf("%s: %d transactions, $%.2f total, avg: $%.2f\n",
84 month, stats.Count, stats.Total, stats.Average)
85 }
86}
87
88func calculateStats(transactions []Transaction) TransactionStats {
89 if len(transactions) == 0 {
90 return TransactionStats{}
91 }
92
93 stats := TransactionStats{
94 Count: len(transactions),
95 Min: transactions[0].Amount,
96 Max: transactions[0].Amount,
97 }
98
99 for _, tx := range transactions {
100 stats.Total += tx.Amount
101 if tx.Amount < stats.Min {
102 stats.Min = tx.Amount
103 }
104 if tx.Amount > stats.Max {
105 stats.Max = tx.Amount
106 }
107 }
108
109 stats.Average = stats.Total / float64(stats.Count)
110 return stats
111}
112
113func main() {
114 // Sample transaction data
115 transactions := []Transaction{
116 {"1", 29.99, "Electronics", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC), "user1", "credit"},
117 {"2", 15.50, "Books", time.Date(2024, 1, 16, 0, 0, 0, 0, time.UTC), "user1", "debit"},
118 {"3", 199.99, "Electronics", time.Date(2024, 1, 18, 0, 0, 0, 0, time.UTC), "user2", "credit"},
119 {"4", 45.00, "Clothing", time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC), "user2", "paypal"},
120 {"5", 89.99, "Electronics", time.Date(2024, 2, 5, 0, 0, 0, 0, time.UTC), "user1", "credit"},
121 {"6", 25.00, "Food", time.Date(2024, 2, 8, 0, 0, 0, 0, time.UTC), "user3", "debit"},
122 {"7", 350.00, "Electronics", time.Date(2024, 2, 10, 0, 0, 0, 0, time.UTC), "user3", "credit"},
123 {"8", 12.99, "Books", time.Date(2024, 2, 12, 0, 0, 0, 0, time.UTC), "user2", "debit"},
124 }
125
126 analyzeTransactions(transactions)
127}
Exercise 6: Map as Data Index
🎯 Learning Objectives: Implement multi-field indexing using maps for efficient data retrieval.
🌍 Real-World Context: Data indexing is crucial for search functionality and database operations. E-commerce sites index products by category, brand, and price range. Social media platforms index content by hashtags, users, and engagement metrics. Search engines build complex indexes for fast retrieval.
⭐ Difficulty: Advanced | ⏱️ Time Estimate: 30 minutes
Create an indexing system for products that allows fast lookup by multiple criteria.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7 "sync"
8)
9
10type Product struct {
11 ID string
12 Name string
13 Category string
14 Price float64
15 Brand string
16 Tags []string
17 InStock bool
18 Rating float64
19}
20
21type ProductIndex struct {
22 // Primary storage
23 products map[string]Product
24
25 // Secondary indexes
26 byCategory map[string][]string // category -> product IDs
27 byBrand map[string][]string // brand -> product IDs
28 byTag map[string][]string // tag -> product IDs
29 byPrice map[string][]string // price range -> product IDs
30
31 mu sync.RWMutex
32}
33
34func NewProductIndex() *ProductIndex {
35 return &ProductIndex{
36 products: make(map[string]Product),
37 byCategory: make(map[string][]string),
38 byBrand: make(map[string][]string),
39 byTag: make(map[string][]string),
40 byPrice: make(map[string][]string),
41 }
42}
43
44func (pi *ProductIndex) AddProduct(product Product) {
45 pi.mu.Lock()
46 defer pi.mu.Unlock()
47
48 // Add to primary storage
49 pi.products[product.ID] = product
50
51 // Update secondary indexes
52 pi.byCategory[product.Category] = append(pi.byCategory[product.Category], product.ID)
53 pi.byBrand[product.Brand] = append(pi.byBrand[product.Brand], product.ID)
54
55 for _, tag := range product.Tags {
56 pi.byTag[tag] = append(pi.byTag[tag], product.ID)
57 }
58
59 priceRange := pi.getPriceRange(product.Price)
60 pi.byPrice[priceRange] = append(pi.byPrice[priceRange], product.ID)
61}
62
63func (pi *ProductIndex) getPriceRange(price float64) string {
64 switch {
65 case price < 20:
66 return "0-20"
67 case price < 50:
68 return "20-50"
69 case price < 100:
70 return "50-100"
71 case price < 200:
72 return "100-200"
73 default:
74 return "200+"
75 }
76}
77
78func (pi *ProductIndex) GetByID(id string) (Product, bool) {
79 pi.mu.RLock()
80 defer pi.mu.RUnlock()
81
82 product, exists := pi.products[id]
83 return product, exists
84}
85
86func (pi *ProductIndex) FindByCategory(category string) []Product {
87 pi.mu.RLock()
88 defer pi.mu.RUnlock()
89
90 var products []Product
91 for _, id := range pi.byCategory[category] {
92 if product, exists := pi.products[id]; exists {
93 products = append(products, product)
94 }
95 }
96 return products
97}
98
99func (pi *ProductIndex) FindByBrand(brand string) []Product {
100 pi.mu.RLock()
101 defer pi.mu.RUnlock()
102
103 var products []Product
104 for _, id := range pi.byBrand[brand] {
105 if product, exists := pi.products[id]; exists {
106 products = append(products, product)
107 }
108 }
109 return products
110}
111
112func (pi *ProductIndex) FindByTag(tag string) []Product {
113 pi.mu.RLock()
114 defer pi.mu.RUnlock()
115
116 var products []Product
117 for _, id := range pi.byTag[tag] {
118 if product, exists := pi.products[id]; exists {
119 products = append(products, product)
120 }
121 }
122 return products
123}
124
125func (pi *ProductIndex) FindByPriceRange(minPrice, maxPrice float64) []Product {
126 pi.mu.RLock()
127 defer pi.mu.RUnlock()
128
129 var products []Product
130 for _, product := range pi.products {
131 if product.Price >= minPrice && product.Price <= maxPrice {
132 products = append(products, product)
133 }
134 }
135 return products
136}
137
138func (pi *ProductIndex) Search(query string) []Product {
139 pi.mu.RLock()
140 defer pi.mu.RUnlock()
141
142 query = strings.ToLower(query)
143 var results []Product
144
145 for _, product := range pi.products {
146 // Simple text search in name and brand
147 if strings.Contains(strings.ToLower(product.Name), query) ||
148 strings.Contains(strings.ToLower(product.Brand), query) {
149 results = append(results, product)
150 continue
151 }
152
153 // Search in tags
154 for _, tag := range product.Tags {
155 if strings.Contains(strings.ToLower(tag), query) {
156 results = append(results, product)
157 break
158 }
159 }
160 }
161
162 return results
163}
164
165func (pi *ProductIndex) GetIndexStats() map[string]int {
166 pi.mu.RLock()
167 defer pi.mu.RUnlock()
168
169 return map[string]int{
170 "total_products": len(pi.products),
171 "categories": len(pi.byCategory),
172 "brands": len(pi.byBrand),
173 "tags": len(pi.byTag),
174 }
175}
176
177func main() {
178 index := NewProductIndex()
179
180 fmt.Println("=== Product Index System ===")
181
182 // Add sample products
183 products := []Product{
184 {"p1", "Laptop Pro", "Electronics", 999.99, "TechCo", []string{"laptop", "computer", "work"}, true, 4.5},
185 {"p2", "Wireless Mouse", "Electronics", 29.99, "TechCo", []string{"mouse", "wireless", "computer"}, true, 4.2},
186 {"p3", "Programming Book", "Books", 45.00, "TechPress", []string{"programming", "go", "software"}, true, 4.8},
187 {"p4", "Running Shoes", "Clothing", 89.99, "SportMax", []string{"running", "shoes", "fitness"}, true, 4.1},
188 {"p5", "Smart Watch", "Electronics", 199.99, "TechCo", []string{"watch", "smart", "fitness"}, false, 3.9},
189 {"p6", "Coffee Maker", "Kitchen", 79.99, "BrewMaster", []string{"coffee", "kitchen", "appliance"}, true, 4.3},
190 }
191
192 for _, product := range products {
193 index.AddProduct(product)
194 }
195
196 // Test different search methods
197 fmt.Println("\n=== Search Examples ===")
198
199 // Search by category
200 electronics := index.FindByCategory("Electronics")
201 fmt.Printf("Electronics products (%d):\n", len(electronics))
202 for _, product := range electronics {
203 fmt.Printf(" - %s ($%.2f)\n", product.Name, product.Price)
204 }
205
206 // Search by brand
207 techco := index.FindByBrand("TechCo")
208 fmt.Printf("\nTechCo products (%d):\n", len(techco))
209 for _, product := range techco {
210 fmt.Printf(" - %s (%s)\n", product.Name, product.Category)
211 }
212
213 // Search by tag
214 fitness := index.FindByTag("fitness")
215 fmt.Printf("\nFitness products (%d):\n", len(fitness))
216 for _, product := range fitness {
217 fmt.Printf(" - %s\n", product.Name)
218 }
219
220 // Search by price range
221 affordable := index.FindByPriceRange(0, 100)
222 fmt.Printf("\nAffordable products (%d):\n", len(affordable))
223 for _, product := range affordable {
224 fmt.Printf(" - %s ($%.2f)\n", product.Name, product.Price)
225 }
226
227 // Text search
228 searchResults := index.Search("laptop")
229 fmt.Printf("\nSearch results for 'laptop' (%d):\n", len(searchResults))
230 for _, product := range searchResults {
231 fmt.Printf(" - %s - %s (%s)\n", product.Name, product.Brand, strings.Join(product.Tags, ", "))
232 }
233
234 // Index statistics
235 stats := index.GetIndexStats()
236 fmt.Printf("\nIndex Statistics:\n")
237 for metric, value := range stats {
238 fmt.Printf(" %s: %d\n", metric, value)
239 }
240}
Summary
Key Takeaways
- Hash Table Implementation: Maps are hash tables with O(1) average operations
- Key Requirements: Keys must be comparable (support
==operator) - Dynamic Growth: Maps automatically resize to maintain performance
- Value Types: Map values can be any type, including functions and pointers
- Zero Values: Maps return zero values for missing keys
- Concurrency: Regular maps aren't thread-safe, use sync.Map or mutexes
- Memory Overhead: Maps use more memory than arrays but provide flexibility
- Performance: Pre-allocation and proper key selection improve performance
Best Practices
- Pre-allocate capacity when you know approximate map size
- Use simple key types (int, string) for better performance
- Check existence with two-value assignment for safety
- Initialize nil maps before use to avoid panics
- Use sync.Map for simple concurrent access scenarios
- Use mutex for complex concurrent operations
- Consider set semantics using empty struct values (
struct{}{}) - Profile map usage to optimize for your specific workload
When to Use Maps vs Other Data Structures
Use Maps When:
- You need fast key-based lookups (O(1) average)
- Keys are unique or you need to handle collisions
- You need dynamic insertion and deletion
- You're implementing caching, indexing, or lookup tables
- You need to represent relationships between entities
Use Other Structures When:
- Ordered iteration is required (use slice with sorting)
- Memory efficiency is critical and keys are sequential (use slice)
- You need fixed-size collections (use array)
- You need thread-safe access without synchronization overhead (use sync.Map for simple cases)
Next Steps
- Practice: Implement exercises to reinforce concepts
- Explore: Learn about sync.Map and concurrent patterns
- Apply: Use map patterns in your own projects
- Profile: Use Go's profiling tools to optimize map usage
- Advanced: Study hash functions and collision resolution algorithms
Mastering maps gives you powerful tools for building efficient, scalable Go applications!