Maps
## Learning Objectives
- Understand Go maps (hash tables)
- Create and initialize maps
- Add, access, and delete entries
- Iterate over maps
- Handle missing keys safely
## What is a Map?
A map is a hash table implementation - an unordered collection of key-value pairs where keys are unique.
## Declaration
### With make
```go
ages := make(map[string]int)
ages["Alice"] = 30
ages["Bob"] = 25
```
### With Literals
```go
ages := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 35,
}
```
### nil Map
```go
var ages map[string]int // nil map (cannot add to it)
ages = make(map[string]int) // Initialize before use
```
## Basic Operations
### Add/Update
```go
ages := make(map[string]int)
ages["Alice"] = 30 // Add
ages["Alice"] = 31 // Update
```
### Access
```go
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
fmt.Println(ages["Alice"]) // 30
fmt.Println(ages["Unknown"]) // 0 (zero value)
```
### Check Key Exists
```go
ages := map[string]int{
"Alice": 30,
}
value, exists := ages["Alice"]
fmt.Println(value, exists) // 30 true
value, exists = ages["Bob"]
fmt.Println(value, exists) // 0 false
```
### Delete
```go
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
delete(ages, "Bob")
fmt.Println(ages) // map[Alice:30]
```
### Delete Non-existent Key
```go
ages := map[string]int{"Alice": 30}
delete(ages, "Bob") // Safe - no error even if key doesn't exist
```
## Length
```go
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
fmt.Println(len(ages)) // 2
```
## Iteration
### Basic Iteration
```go
ages := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 35,
}
for key, value := range ages {
fmt.Printf("%s: %d\n", key, value)
}
```
### Order
- Map iteration order is **not guaranteed**
- Order may differ between iterations
### Key Only
```go
for key := range ages {
fmt.Println(key)
}
```
### Sorted Keys
```go
ages := map[string]int{
"Charlie": 35,
"Alice": 30,
"Bob": 25,
}
keys := make([]string, 0, len(ages))
for key := range ages {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
fmt.Printf("%s: %d\n", key, ages[key])
}
```
## Zero Value
### Reading from nil Map
```go
var ages map[string]int // nil
fmt.Println(ages["Alice"]) // 0
fmt.Println(len(ages)) // 0
delete(ages, "Alice") // Safe
// ages["Alice"] = 30 // PANIC!
```
### Check Before Write
```go
var ages map[string]int
if ages == nil {
ages = make(map[string]int)
}
ages["Alice"] = 30 // Now safe
```
## Map as Reference
Maps are reference types - copying a map shares the underlying data.
```go
ages1 := map[string]int{"Alice": 30}
ages2 := ages1
ages2["Alice"] = 31
fmt.Println(ages1["Alice"]) // 31 (both reference same map)
fmt.Println(ages2["Alice"]) // 31
```
## Pointers to Maps
```go
func modify(m map[string]int) {
m["Alice"] = 31
}
ages := map[string]int{"Alice": 30}
modify(ages)
fmt.Println(ages["Alice"]) // 31
```
## Common Patterns
### Word Count
```go
text := "hello world hello go programming hello"
words := strings.Fields(text)
count := make(map[string]int)
for _, word := range words {
count[word]++
}
fmt.Println(count) // map[hello:3 world:1 go:1 programming:1]
```
### Set Implementation
```go
type Set struct {
items map[string]struct{}
}
func NewSet() *Set {
return &Set{make(map[string]struct{})}
}
func (s *Set) Add(item string) {
s.items[item] = struct{}{}
}
func (s *Set) Contains(item string) bool {
_, exists := s.items[item]
return exists
}
func (s *Set) Remove(item string) {
delete(s.items, item)
}
func main() {
set := NewSet()
set.Add("apple")
set.Add("banana")
fmt.Println(set.Contains("apple")) // true
fmt.Println(set.Contains("orange")) // false
}
```
### Group By
```go
people := []struct {
Name string
Age int
}{
{"Alice", 30},
{"Bob", 25},
{"Carol", 30},
{"David", 25},
}
groups := make(map[int][]string)
for _, p := range people {
groups[p.Age] = append(groups[p.Age], p.Name)
}
fmt.Println(groups)
// map[25:[Bob David] 30:[Alice Carol]]
```
### Unique Values
```go
func unique(ints []int) []int {
seen := make(map[int]bool)
result := []int{}
for _, n := range ints {
if !seen[n] {
seen[n] = true
result = append(result, n)
}
}
return result
}
```
## Comparison
### Maps Cannot Be Compared
```go
m1 := map[string]int{"a": 1}
m2 := map[string]int{"a": 1}
// m1 == m2 // COMPILE ERROR: map can only be compared to nil
```
### Deep Compare
```go
func equalMaps(m1, m2 map[string]int) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}
```
## Concurrent Access
### Race Condition
Maps are not safe for concurrent access by default.
```go
var counter = make(map[string]int)
// This is unsafe!
go func() {
for i := 0; i < 1000; i++ {
counter["a"]++
}
}()
go func() {
for i := 0; i < 1000; i++ {
counter["a"]++
}
}()
```
### sync.RWMutex
```go
import "sync"
var counter = struct {
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}
counter.Lock()
counter.m["a"]++
counter.Unlock()
counter.RLock()
fmt.Println(counter.m["a"])
counter.RUnlock()
```
### sync.Map
```go
var syncMap sync.Map
syncMap.Store("a", 1)
value, ok := syncMap.Load("a")
syncMap.Delete("a")
syncMap.Range(func(key, value interface{}) bool {
fmt.Printf("%s: %d\n", key, value)
return true
})
```
## Summary
- Maps are hash tables - key-value pairs with unique keys
- Create with `make()` or map literals
- Zero value is `nil` - cannot add to nil map
- Reading non-existent key returns zero value
- Use comma-ok idiom to check key existence
- `delete()` is safe even if key doesn't exist
- Iteration order is not guaranteed
- Maps are reference types
- Not safe for concurrent access - use sync.RWMutex or sync.Map
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →