Skip to main content

Go Range

Introduction

The range keyword in Go provides a convenient way to iterate over elements in various data structures like arrays, slices, maps, strings, and channels. It's one of the most commonly used features in Go programming and is essential for processing collections of data efficiently.

Unlike traditional for loops that require manual indexing and boundary checks, range simplifies the process by automatically handling these details for you. This allows you to focus on what you want to do with each element rather than the mechanics of iteration.

Basic Syntax

The basic syntax of a range loop in Go is:

go
for key, value := range collection {
// Use key and value
}

Depending on the type of collection being iterated over, key and value have different meanings:

Collection TypeKeyValue
Array/SliceIndex (int)Element at that index
StringIndex (int)Unicode code point (rune)
MapMap key (any type)Map value (any type)
ChannelNot usedElement received from channel

Iterating Over Arrays and Slices

When using range with arrays or slices, the first variable is the index, and the second is a copy of the element at that index.

go
package main

import "fmt"

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

fmt.Println("Fruits list:")
for index, fruit := range fruits {
fmt.Printf("%d: %s
", index, fruit)
}
}

Output:

Fruits list:
0: apple
1: banana
2: cherry
3: date

Using Only the Index

If you only need the index, you can omit the value variable:

go
package main

import "fmt"

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

fmt.Println("Fruit indexes:")
for i := range fruits {
fmt.Printf("Index %d
", i)
}
}

Output:

Fruit indexes:
Index 0
Index 1
Index 2
Index 3

Using Only the Value

If you only need the element and not the index, you can use the blank identifier (_) to ignore the index:

go
package main

import "fmt"

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

fmt.Println("Just the fruits:")
for _, fruit := range fruits {
fmt.Println(fruit)
}
}

Output:

Just the fruits:
apple
banana
cherry
date

Iterating Over Strings

When ranging over a string, range provides the index and the Unicode code point (rune) at that position, not the byte:

go
package main

import "fmt"

func main() {
message := "Hello, 世界"

fmt.Println("Characters in the message:")
for i, char := range message {
fmt.Printf("Index %d: %c (Unicode: %U)
", i, char, char)
}
}

Output:

Characters in the message:
Index 0: H (Unicode: U+0048)
Index 1: e (Unicode: U+0065)
Index 2: l (Unicode: U+006C)
Index 3: l (Unicode: U+006C)
Index 4: o (Unicode: U+006F)
Index 5: , (Unicode: U+002C)
Index 6: (Unicode: U+0020)
Index 7: 世 (Unicode: U+4E16)
Index 10: 界 (Unicode: U+754C)

Notice that the indices for the Chinese characters jump because each character occupies multiple bytes.

Iterating Over Maps

When using range with maps, the first variable is the key, and the second is a copy of the value for that key.

go
package main

import "fmt"

func main() {
scores := map[string]int{
"Alice": 92,
"Bob": 85,
"Charlie": 79,
"Diana": 96,
}

fmt.Println("Student scores:")
for name, score := range scores {
fmt.Printf("%s: %d
", name, score)
}
}

Output:

Student scores:
Alice: 92
Bob: 85
Charlie: 79
Diana: 96

Note: Map iteration order is not guaranteed in Go. The order may vary between iterations.

Iterating Over Channels

You can also use range to read values from a channel until it is closed:

go
package main

import "fmt"

func main() {
// Create a channel and close it after sending values
ch := make(chan int)
go func() {
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}()

// Range over the channel
fmt.Println("Values from channel:")
for num := range ch {
fmt.Println(num)
}
}

Output:

Values from channel:
1
2
3
4
5

The loop automatically exits when the channel is closed.

Important Characteristics

Value Safety

When iterating with range, each value is a copy of the element in the collection, not a reference to it. This means modifying the value variable does not affect the original collection:

go
package main

import "fmt"

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

for _, num := range numbers {
num = num * 10 // This doesn't affect the original slice
}

fmt.Println("After attempted modification:", numbers)
}

Output:

After attempted modification: [1 2 3 4 5]

To modify the original collection, you need to use the index:

go
package main

import "fmt"

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

for i := range numbers {
numbers[i] *= 10 // This modifies the original slice
}

fmt.Println("After modification:", numbers)
}

Output:

After modification: [10 20 30 40 50]

Evaluation Time

The range expression is evaluated once before the loop begins. Subsequent changes to the collection may not affect the iterations:

go
package main

import "fmt"

func main() {
slice := []int{1, 2, 3}

for i, num := range slice {
fmt.Printf("Index: %d, Value: %d
", i, num)

if i == 0 {
slice = append(slice, 4, 5)
fmt.Println("Slice modified:", slice)
}
}
}

Output:

Index: 0, Value: 1
Slice modified: [1 2 3 4 5]
Index: 1, Value: 2
Index: 2, Value: 3

Notice that even though we added elements to the slice during iteration, the range loop only processes the original three elements.

Practical Examples

Example 1: Finding the Sum and Average of Numbers

go
package main

import "fmt"

func main() {
numbers := []int{78, 93, 55, 87, 42, 68, 92}

sum := 0
for _, num := range numbers {
sum += num
}

average := float64(sum) / float64(len(numbers))

fmt.Printf("Numbers: %v
", numbers)
fmt.Printf("Sum: %d
", sum)
fmt.Printf("Average: %.2f
", average)
}

Output:

Numbers: [78 93 55 87 42 68 92]
Sum: 515
Average: 73.57

Example 2: Counting Word Frequency

go
package main

import (
"fmt"
"strings"
)

func main() {
text := "Go is a programming language designed at Google. Go is statically typed and produces compiled machine code binaries."

// Split the text into words and convert to lowercase
words := strings.Fields(strings.ToLower(text))

// Count word frequencies
frequency := make(map[string]int)
for _, word := range words {
// Remove punctuation
word = strings.Trim(word, ".,!?;:")
frequency[word]++
}

// Display word frequencies
fmt.Println("Word frequency:")
for word, count := range frequency {
fmt.Printf("%-12s: %d
", word, count)
}
}

Output:

Word frequency:
go : 2
is : 2
a : 1
programming : 1
language : 1
designed : 1
at : 1
google : 1
statically : 1
typed : 1
and : 1
produces : 1
compiled : 1
machine : 1
code : 1
binaries : 1

Example 3: Building a Simple Data Processing Pipeline

go
package main

import "fmt"

func main() {
// Sample data: Student scores in different subjects
studentScores := map[string]map[string]int{
"Alice": {
"Math": 92,
"English": 88,
"Science": 95,
},
"Bob": {
"Math": 78,
"English": 85,
"Science": 80,
},
"Charlie": {
"Math": 90,
"English": 72,
"Science": 88,
},
}

// Calculate and print average score for each student
fmt.Println("Student averages:")
for student, scores := range studentScores {
sum := 0
count := 0

for _, score := range scores {
sum += score
count++
}

average := float64(sum) / float64(count)
fmt.Printf("%s: %.2f
", student, average)
}

// Calculate and print average score for each subject
subjectTotals := map[string]int{}
subjectCounts := map[string]int{}

for _, scores := range studentScores {
for subject, score := range scores {
subjectTotals[subject] += score
subjectCounts[subject]++
}
}

fmt.Println("
Subject averages:")
for subject, total := range subjectTotals {
average := float64(total) / float64(subjectCounts[subject])
fmt.Printf("%s: %.2f
", subject, average)
}
}

Output:

Student averages:
Alice: 91.67
Bob: 81.00
Charlie: 83.33

Subject averages:
Math: 86.67
English: 81.67
Science: 87.67

Visualizing Range in Action

flowchart TB Start([Start]) --> Init[Initialize Collection] Init --> Loop[range Loop Start] Loop --> Check[More items?] Check -->|Yes| Process[Process item] Process --> NextIter[Next Iteration] NextIter --> Check Check -->|No| End([End])

class Start,End primary class Loop,Check,Process emphasis classDef primary fill:#93c5fd,stroke:#2563eb,color:#1e3a8a classDef emphasis fill:#dbeafe,stroke:#93c5fd,color:#1e40af

Summary

The range keyword in Go provides a clean, efficient way to iterate through different data types:

  • Arrays/Slices: Gives index and element value
  • Strings: Gives index and Unicode code point (rune)
  • Maps: Gives key and value
  • Channels: Gives values until the channel is closed

Key things to remember:

  1. Values returned by range are copies, not references
  2. You can ignore either the key or value using the blank identifier _
  3. The range expression is evaluated once before the loop begins
  4. Map iteration order is not guaranteed
  5. When iterating over strings, range works with runes, not bytes

Understanding range is crucial for writing idiomatic Go code that processes collections efficiently.

Practice Exercises

  1. Basic Range Usage: Create a program that finds the maximum and minimum values in a slice of integers using range.

  2. String Manipulation: Write a function that counts the number of vowels in a string using range.

  3. Map Processing: Create a program that filters a map based on certain criteria and creates a new map with the filtered results.

  4. Two-Dimensional Slices: Use nested range loops to process a 2D slice representing a grid or matrix.

  5. Challenge: Implement a function that determines if two strings are anagrams of each other using range and maps.

Additional Resources

Happy coding with Go's range keyword!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)