Maps in Golang

Maps are a built-in data structure in Go that associate keys with values. They’re similar to dictionaries in other languages like Python or hash tables in Java. Maps are unordered collections, and they’re extremely useful for storing and retrieving data quickly.

Creating Maps

You can create maps using the make function or with a map literal.

Using make()

Syntax
var m map[keyType]valueType
m = make(map[keyType]valueType)
Example
package main

import "fmt"

func main() {
    // Create an empty map
    ages := make(map[string]int)
    
    // Add some key-value pairs
    ages["Alice"] = 25
    ages["Bob"] = 30
    ages["Charlie"] = 35
    
    fmt.Println("Ages:", ages)
}

Using Map Literals

Syntax
m := map[keyType]valueType{
    key1: value1,
    key2: value2,
    ...
}
Example
package main

import "fmt"

func main() {
    // Create a map with initial values
    colors := map[string]string{
        "red":   "#FF0000",
        "green": "#00FF00",
        "blue":  "#0000FF",
    }
    
    fmt.Println("Colors:", colors)
}

Accessing Map Values

You can access values using square brackets with the key.

Example
package main

import "fmt"

func main() {
    capitals := map[string]string{
        "France":  "Paris",
        "Germany": "Berlin",
        "Italy":   "Rome",
    }
    
    fmt.Println("Capital of France:", capitals["France"])
    fmt.Println("Capital of Spain:", capitals["Spain"]) // Will be empty string
}

Checking if a Key Exists

Maps return the zero value for non-existent keys. To check if a key exists, use the comma ok idiom.

Example
package main

import "fmt"

func main() {
    scores := map[string]int{
        "Alice": 95,
        "Bob":   87,
    }
    
    // Check if key exists
    if score, exists := scores["Alice"]; exists {
        fmt.Println("Alice's score:", score)
    } else {
        fmt.Println("Alice not found")
    }
    
    // Check for non-existent key
    if score, exists := scores["Charlie"]; exists {
        fmt.Println("Charlie's score:", score)
    } else {
        fmt.Println("Charlie not found")
    }
}

Modifying Maps

Adding and Updating Values

Example
package main

import "fmt"

func main() {
    inventory := make(map[string]int)
    
    // Add items
    inventory["apples"] = 10
    inventory["bananas"] = 5
    
    fmt.Println("Initial inventory:", inventory)
    
    // Update existing item
    inventory["apples"] = 15
    
    // Add new item
    inventory["oranges"] = 8
    
    fmt.Println("Updated inventory:", inventory)
}

Deleting Entries

Use the delete function to remove entries from a map.

Syntax
delete(map, key)
Example
package main

import "fmt"

func main() {
    products := map[string]float64{
        "laptop":  999.99,
        "mouse":   25.50,
        "keyboard": 75.00,
    }
    
    fmt.Println("Before deletion:", products)
    
    // Delete an item
    delete(products, "mouse")
    
    fmt.Println("After deletion:", products)
    
    // Try to delete non-existent key (safe operation)
    delete(products, "monitor")
    fmt.Println("After attempting to delete non-existent key:", products)
}

Iterating Over Maps

Use the range keyword to iterate over maps. Note that iteration order is not guaranteed.

Example
package main

import "fmt"

func main() {
    studentGrades := map[string]int{
        "Alice":   95,
        "Bob":     87,
        "Charlie": 92,
        "Diana":   88,
    }
    
    fmt.Println("Student grades:")
    for name, grade := range studentGrades {
        fmt.Printf("%s: %d\n", name, grade)
    }
    
    // Iterate only keys
    fmt.Println("\nStudent names:")
    for name := range studentGrades {
        fmt.Println(name)
    }
    
    // Iterate only values
    fmt.Println("\nGrades:")
    for _, grade := range studentGrades {
        fmt.Println(grade)
    }
}

Map Length

Use the len function to get the number of key-value pairs in a map.

Example
package main

import "fmt"

func main() {
    data := map[string]string{
        "name":    "John Doe",
        "email":   "[email protected]",
        "phone":   "123-456-7890",
        "address": "123 Main St",
    }
    
    fmt.Printf("Number of fields: %d\n", len(data))
}

Nested Maps

You can create maps of maps for more complex data structures.

Example
package main

import "fmt"

func main() {
    // Map of maps
    students := map[string]map[string]interface{}{
        "alice": {
            "age":    20,
            "major":  "Computer Science",
            "gpa":    3.8,
        },
        "bob": {
            "age":    22,
            "major":  "Mathematics",
            "gpa":    3.5,
        },
    }
    
    fmt.Println("Alice's age:", students["alice"]["age"])
    fmt.Println("Bob's major:", students["bob"]["major"])
    
    // Add a new field
    students["alice"]["graduation_year"] = 2024
    
    fmt.Println("Updated Alice:", students["alice"])
}

Map Operations and Best Practices

Copying Maps

Maps are reference types. To copy a map, you need to create a new one and copy elements.

Example
package main

import "fmt"

func main() {
    original := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }
    
    // Wrong way - this creates a reference
    copy1 := original
    copy1["a"] = 999
    fmt.Println("Original after modifying copy1:", original)
    
    // Right way - create new map and copy elements
    copy2 := make(map[string]int)
    for k, v := range original {
        copy2[k] = v
    }
    copy2["a"] = 777
    fmt.Println("Original after modifying copy2:", original)
    fmt.Println("Copy2:", copy2)
}

Maps as Function Parameters

Maps are passed by reference, so modifications in functions affect the original map.

Example
package main

import "fmt"

func updateMap(m map[string]int, key string, value int) {
    m[key] = value
}

func main() {
    data := map[string]int{"x": 10, "y": 20}
    
    fmt.Println("Before:", data)
    updateMap(data, "z", 30)
    fmt.Println("After:", data)
}

Thread Safety

Maps are not thread-safe. For concurrent access, use sync.Mutex or sync.RWMutex.

Example
package main

import (
    "fmt"
    "sync"
)

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

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

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

func main() {
    sm := &SafeMap{
        data: make(map[string]int),
    }
    
    sm.Set("counter", 0)
    
    // This would be safe to call from multiple goroutines
    if value, exists := sm.Get("counter"); exists {
        fmt.Println("Counter:", value)
    }
}

Common Use Cases

Counting Frequencies

Example
package main

import (
    "fmt"
    "strings"
)

func countWords(text string) map[string]int {
    words := strings.Fields(text)
    counts := make(map[string]int)
    
    for _, word := range words {
        // Convert to lowercase for case-insensitive counting
        word = strings.ToLower(word)
        counts[word]++
    }
    
    return counts
}

func main() {
    text := "The quick brown fox jumps over the lazy dog"
    wordCounts := countWords(text)
    
    fmt.Println("Word frequencies:")
    for word, count := range wordCounts {
        fmt.Printf("%s: %d\n", word, count)
    }
}

Grouping Data

Example
package main

import "fmt"

type Person struct {
    Name string
    City string
}

func groupByCity(people []Person) map[string][]Person {
    groups := make(map[string][]Person)
    
    for _, person := range people {
        groups[person.City] = append(groups[person.City], person)
    }
    
    return groups
}

func main() {
    people := []Person{
        {"Alice", "New York"},
        {"Bob", "Los Angeles"},
        {"Charlie", "New York"},
        {"Diana", "Chicago"},
        {"Eve", "Los Angeles"},
    }
    
    grouped := groupByCity(people)
    
    for city, residents := range grouped {
        fmt.Printf("%s residents:\n", city)
        for _, person := range residents {
            fmt.Printf("  - %s\n", person.Name)
        }
    }
}

Caching

Example
package main

import (
    "fmt"
    "time"
)

type Cache struct {
    data map[string]interface{}
}

func NewCache() *Cache {
    return &Cache{
        data: make(map[string]interface{}),
    }
}

func (c *Cache) Get(key string) (interface{}, bool) {
    value, exists := c.data[key]
    return value, exists
}

func (c *Cache) Set(key string, value interface{}) {
    c.data[key] = value
}

// Simulate expensive computation
func expensiveOperation(n int) int {
    time.Sleep(100 * time.Millisecond) // Simulate delay
    return n * n
}

func main() {
    cache := NewCache()
    
    // First call - compute and cache
    start := time.Now()
    result1 := expensiveOperation(5)
    cache.Set("5", result1)
    fmt.Printf("First call took %v, result: %d\n", time.Since(start), result1)
    
    // Second call - use cache
    start = time.Now()
    if cached, exists := cache.Get("5"); exists {
        result2 := cached.(int)
        fmt.Printf("Cached call took %v, result: %d\n", time.Since(start), result2)
    }
}

External Resources:

Related Tutorials:

Last updated on