Loading...
Loading...

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?

  • A runtime panic occurs
  • Returns the zero value for the value type
  • The key is automatically created

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?

  • Using the in keyword
  • The two-value assignment idiom
  • Calling contains()

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?

  • It must implement String()
  • All fields must be comparable
  • It needs special annotations

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?

  • For single-threaded applications
  • Highly concurrent read-heavy workloads
  • When you need maximum type safety

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 make when size is known
  • Use pointers for large values to avoid copying
  • Consider string vs []byte key tradeoffs
  • Benchmark map alternatives (slices, custom hash tables) for extreme cases

Performance Quiz

Why pre-size a map with make?

  • To fix the maximum size
  • To minimize rehashing during growth
  • To make it immutable

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?

  • Using a slice
  • map[ValueType]bool
  • A linked list

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 float64 by default
  • For structured data, prefer structs over maps

JSON Quiz

Why use map[string]interface{} for JSON parsing?

  • It's faster than structs
  • To handle arbitrary JSON structures
  • It prevents type assertions
0 Interaction
0 Views
Views
0 Likes
×
×
×
🍪 CookieConsent@Ptutorials:~

Welcome to Ptutorials

$ Allow cookies on this site ? (y/n)

top-home