Foreach Loop in Go: Range, Iterate, and Modern Patterns
Table of Contents
Go doesn’t have a foreach keyword like other languages, but it has something even better - the for...range loop. This powerful construct lets you iterate over slices, arrays, maps, strings, channels, and even integers (since Go 1.22). It’s cleaner, safer, and more idiomatic than traditional C-style loops.
In this guide, you’ll learn all the ways to iterate in Go, including the latest features from Go 1.22+ that make loops safer and more expressive. π
Basic Range Syntax #
Using the range keyword, you can create the range form of the for loop that is very useful when iterating over a slice or map. This kind of loop has the form of:
for <index>, <value> := range <array/slice> {
...
}
where:
<index>is a numeric ordinal number that returns 0 for the first element in the array, 1 for the second, and so on<value>is a copy of a slice/array element at that<index>
For maps, the for range loop has <key> instead of <index>:
for <key>, <value> := range <map> {
...
}
where:
<key>is the key of a given map entry<value>is a copy of a map element at that<key>
Basic Examples #
Iterating Over Slices #
In the following example, we iterate through a slice to print each element. We don’t need the index, so we ignore it using the blank identifier (underscore):
fruits := []string{"apple", "strawberry", "raspberry"}
for _, fruit := range fruits {
fmt.Printf("Fruit: %s\n", fruit)
}
Output:
Fruit: apple
Fruit: strawberry
Fruit: raspberry
Iterating Over Maps #
When iterating over maps, you get both the key and value:
fruitColors := map[string]string{
"apple": "green",
"strawberry": "red",
"raspberry": "pink",
}
for fruit, color := range fruitColors {
fmt.Printf("%s color is %s\n", fruit, color)
}
Output:
apple color is green
strawberry color is red
raspberry color is pink
Modern Go Features (Go 1.22+) #
Go 1.22 introduced game-changing improvements to for loops that make your code cleaner and safer.
Range Over Integers #
Since Go 1.22, you can range directly over integers - no more for i := 0; i < n; i++!
// Old way (still works)
for i := 0; i < 5; i++ {
fmt.Printf("Iteration %d\n", i)
}
// New way (Go 1.22+) β¨
for i := range 5 {
fmt.Printf("Iteration %d\n", i)
}
Both output:
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
This is especially useful for simple counting loops and makes the code more readable.
Loop Variable Scoping (Important!) #
Major improvement in Go 1.22: Loop variables are now per-iteration scoped. This fixes a common bug that plagued Go developers for years.
// Before Go 1.22 - BUG! All goroutines would print the same value
values := []int{1, 2, 3, 4, 5}
for _, v := range values {
go func() {
fmt.Println(v) // Would often print 5, 5, 5, 5, 5
}()
}
// Go 1.22+ - FIXED! Each goroutine gets its own copy
for _, v := range values {
go func() {
fmt.Println(v) // Correctly prints 1, 2, 3, 4, 5
}()
}
Note: If your go.mod has go 1.22 or later, this fix is automatically enabled. No code changes needed! π
Range Over Functions (Go 1.23+) #
Go 1.23 introduced the ability to range over custom iterator functions:
// Custom iterator that yields even numbers
func evenNumbers(max int) func(yield func(int) bool) {
return func(yield func(int) bool) {
for i := 0; i < max; i += 2 {
if !yield(i) {
return
}
}
}
}
// Use it like a regular range loop
for num := range evenNumbers(10) {
fmt.Println(num) // Prints: 0, 2, 4, 6, 8
}
Common Mistakes #
1. Assuming Map Iteration Order #
Problem: Map iteration order is randomized for security reasons.
m := map[string]int{"a": 1, "b": 2, "c": 3}
// β Wrong - order is not guaranteed
for k, v := range m {
fmt.Println(k, v) // Order may vary between runs
}
// β
Correct - sort keys if you need consistent order
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k]) // Guaranteed alphabetical order
}
2. Modifying Slice During Iteration #
numbers := []int{1, 2, 3, 4, 5}
// β Dangerous - can skip elements or panic
for i, num := range numbers {
if num%2 == 0 {
numbers = append(numbers[:i], numbers[i+1:]...) // Modifying while iterating
}
}
// β
Correct - iterate backwards or use a separate result slice
result := make([]int, 0, len(numbers))
for _, num := range numbers {
if num%2 != 0 {
result = append(result, num)
}
}
3. Forgetting Range Returns a Copy #
type Person struct {
Name string
Age int
}
people := []Person{{"Alice", 25}, {"Bob", 30}}
// β Wrong - modifying a copy, original unchanged
for _, p := range people {
p.Age++ // This doesn't modify the original slice
}
// β
Correct - use index to modify original
for i := range people {
people[i].Age++
}
// β
Also correct - use pointer slice
peoplePtr := []*Person{{"Alice", 25}, {"Bob", 30}}
for _, p := range peoplePtr {
p.Age++ // This modifies the original because p is a pointer
}
Performance Considerations #
Use Index-Only Loops for Large Structs #
When iterating over slices of large structs, use index-only iteration to avoid copying:
type LargeStruct struct {
Data [1024]byte
}
items := make([]LargeStruct, 1000)
// β Slower - copies each struct
for _, item := range items {
// item is a copy of items[i]
processStruct(item)
}
// β
Faster - no copies
for i := range items {
// items[i] is accessed directly
processStruct(items[i])
}
Range Over Strings is UTF-8 Aware #
text := "Hello δΈη"
// Iterates over runes (UTF-8 code points), not bytes
for i, r := range text {
fmt.Printf("Position %d: %c (Unicode: %U)\n", i, r, r)
}
Output:
Position 0: H (Unicode: U+0048)
Position 1: e (Unicode: U+0065)
Position 2: l (Unicode: U+006C)
Position 3: l (Unicode: U+006C)
Position 4: o (Unicode: U+006F)
Position 5: (Unicode: U+0020)
Position 6: δΈ (Unicode: U+4E16)
Position 9: η (Unicode: U+754C)
Notice position 6 jumps to 9 because the Chinese characters are multi-byte.
Related Topics #
Master other loop patterns and iteration techniques:
- While loop in Go - Simulate while loops using for
- Infinite loop - Create loops that run forever
- Do-while loop - Execute code at least once
- Break and continue - Control loop flow
- Range over ticker - Iterate with time intervals
Tested with Go 1.25+ | Last verified: December 2025
Happy iterating! π