Skip to content

Go Range: Iteration Mastery

Overview

Master Go's powerful range keyword for elegant iteration across all data structures. This comprehensive guide covers range patterns for arrays, slices, strings, maps, and channels, building upon our understanding of Go's collection types and control flow.

Key Points

  • Universal iteration keyword for all Go collections
  • Returns different values based on data structure type
  • Supports index-only, value-only, and both patterns
  • Essential for idiomatic Go iteration
  • Memory-efficient and performance-optimized

Understanding Range in Go πŸ”„

The range keyword is Go's universal iterator, providing a clean and consistent way to traverse collections. It adapts to different data structures while maintaining a uniform syntax pattern.

Range Return Values

graph TD
    A[range keyword] --> B[Arrays/Slices]
    A --> C[Strings]
    A --> D[Maps]
    A --> E[Channels]
    B --> F[index, value]
    C --> G[byte_index, rune]
    D --> H[key, value]
    E --> I[value only]
    style A fill:#999,stroke:#333,stroke-width:2px,color:#000
    style B fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    style C fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    style D fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
    style E fill:#fff3e0,stroke:#e65100,stroke-width:2px

Range with Arrays and Slices πŸ“¦

Arrays and slices are the most common targets for range iteration, providing both index and value access.

Basic Array and Slice Iteration

Array and Slice Range Patterns

array_slice_range.go
package main

import "fmt"

func main() {
    fruits := []string{"apple", "banana", "cherry", "date"}

    // Iterate with both index and value
    for index, fruit := range fruits {
        fmt.Printf("Index %d: %s\n", index, fruit)
    }

    // Output:
    // Index 0: apple
    // Index 1: banana
    // Index 2: cherry
    // Index 3: date
}
index_only.go
package main

import "fmt"

func main() {
    numbers := []int{10, 20, 30, 40, 50}

    // Iterate over indices only
    for index := range numbers {
        fmt.Printf("Index: %d, Value: %d\n", index, numbers[index])
    }

    // Useful for modifying elements in place
    for i := range numbers {
        numbers[i] *= 2  // Double each value
    }

    fmt.Printf("Doubled: %v\n", numbers)
}
value_only.go
package main

import "fmt"

func main() {
    scores := []int{95, 87, 92, 78, 89}

    // Iterate over values only (ignore index)
    total := 0
    for _, score := range scores {
        total += score
    }

    average := float64(total) / float64(len(scores))
    fmt.Printf("Average score: %.2f\n", average)
    // Output: Average score: 88.20
}

Range with Strings πŸ”€

String iteration with range provides access to Unicode runes, making it perfect for international text processing.

String Iteration Patterns

String Range Operations

string_range.go
package main

import "fmt"

func main() {
    text := "Hello, δΈ–η•Œ!"

    // Iterate over byte index and rune
    for byteIndex, char := range text {
        fmt.Printf("Byte %d: %c (Unicode: U+%04X)\n", byteIndex, char, char)
    }

    // Output:
    // Byte 0: H (Unicode: U+0048)
    // Byte 1: e (Unicode: U+0065)
    // ...
    // Byte 7: δΈ– (Unicode: U+4E16)
    // Byte 10: η•Œ (Unicode: U+754C)
}
chars_only.go
package main

import "fmt"

func main() {
    greeting := "Go语言编程"

    // Iterate over characters only
    fmt.Print("Characters: ")
    for _, char := range greeting {
        fmt.Printf("%c ", char)
    }
    fmt.Println()

    // Output: Characters: G o θ―­ 言 ηΌ– 程
}
unicode_processing.go
package main

import (
    "fmt"
    "unicode"
)

func main() {
    text := "Hello123δΈ–η•Œ!"

    // Categorize characters
    for _, char := range text {
        switch {
        case unicode.IsLetter(char):
            fmt.Printf("%c is a letter\n", char)
        case unicode.IsDigit(char):
            fmt.Printf("%c is a digit\n", char)
        case unicode.IsPunct(char):
            fmt.Printf("%c is punctuation\n", char)
        }
    }
}

Range with Maps πŸ—Ί

Map iteration provides access to key-value pairs, with flexible patterns for different use cases.

Map Iteration Techniques

Map Range Patterns

map_range.go
package main

import "fmt"

func main() {
    inventory := map[string]int{
        "apples": 50, "bananas": 30, "oranges": 25,
    }

    // Iterate over keys and values
    for item, count := range inventory {
        fmt.Printf("%s: %d in stock\n", item, count)
    }

    // Note: Map iteration order is not guaranteed
}
keys_only.go
package main

import "fmt"

func main() {
    settings := map[string]bool{
        "debug": true, "verbose": false, "logging": true,
    }

    // Collect all setting names
    var settingNames []string
    for setting := range settings {
        settingNames = append(settingNames, setting)
    }

    fmt.Printf("Available settings: %v\n", settingNames)
}
values_only.go
package main

import "fmt"

func main() {
    scores := map[string]int{
        "Alice": 95, "Bob": 87, "Charlie": 92,
    }

    // Calculate total score
    total := 0
    for _, score := range scores {
        total += score
    }

    fmt.Printf("Total score: %d\n", total)
}

Advanced Range Techniques βš™

Modifying Collections During Iteration

Safe Modification Patterns

slice_modification.go
package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5}

    // Safe: Modify elements using index
    for i := range numbers {
        numbers[i] = numbers[i] * numbers[i]  // Square each number
    }

    fmt.Printf("Squared: %v\n", numbers)
    // Output: Squared: [1 4 9 16 25]
}
filtering.go
package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // Filter even numbers
    var evens []int
    for _, num := range numbers {
        if num%2 == 0 {
            evens = append(evens, num)
        }
    }

    fmt.Printf("Even numbers: %v\n", evens)
}
nested_iteration.go
package main

import "fmt"

func main() {
    matrix := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

    // Iterate over 2D slice
    for rowIdx, row := range matrix {
        for colIdx, value := range row {
            fmt.Printf("matrix[%d][%d] = %d\n", rowIdx, colIdx, value)
        }
    }
}

Channel Iteration

Channel Range Patterns

channel_range.go
package main

import "fmt"

func main() {
    ch := make(chan int, 3)

    // Send values and close
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    // Range automatically stops when channel is closed
    for value := range ch {
        fmt.Printf("Received: %d\n", value)
    }

    // Output:
    // Received: 1
    // Received: 2
    // Received: 3
}
producer_consumer.go
package main

import "fmt"

func main() {
    ch := make(chan string, 2)

    // Producer goroutine
    go func() {
        defer close(ch)
        for i := 1; i <= 3; i++ {
            ch <- fmt.Sprintf("message-%d", i)
        }
    }()

    // Consumer using range
    for message := range ch {
        fmt.Printf("Processing: %s\n", message)
    }
}

Best Practices and Performance πŸ““

Range Best Practices

  1. Use Blank Identifier Wisely

    1
    2
    3
    4
    5
    6
    // Good: Only get what you need
    for i := range slice { /* index only */ }
    for _, v := range slice { /* value only */ }
    
    // Avoid: Unnecessary assignments
    for i, _ := range slice { /* wasteful */ }
    

  2. Understand Value Copying

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Values are copied - modifications won't affect original
    for _, item := range items {
        item.field = "changed"  // Won't change original
    }
    
    // Use index for modifications
    for i := range items {
        items[i].field = "changed"  // Will change original
    }
    

  3. Handle Map Iteration Order

    // Don't rely on iteration order
    for key, value := range myMap {
        // Order is not guaranteed
    }
    
    // Sort keys if order matters
    keys := make([]string, 0, len(myMap))
    for k := range myMap {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    

Common Pitfalls

  • Loop Variable Capture: In goroutines, capture loop variables properly
  • Slice Modification: Don't modify slice length during iteration
  • String vs Bytes: Range over strings gives runes, not bytes
  • Map Order: Never rely on map iteration order

Quick Reference πŸ“‘

Key Takeaways

  1. Universal Syntax: for key, value := range collection
  2. Flexible Patterns: Use _ to ignore unneeded values
  3. Type-Specific Returns: Different collections return different value types
  4. Safe Iteration: Range handles bounds checking automatically
  5. Unicode Aware: String iteration properly handles Unicode runes
  6. Performance: Efficient iteration without manual index management

Remember

"Range is Go's universal iterator. Master its patterns for each collection type, understand value copying semantics, and always use the blank identifier for values you don't need. When in doubt, range it out!"