Loops
Go has exactly one loop construct: for. There is no while, do-while, or foreach. Despite this, for covers every looping pattern through three forms — and the range keyword handles iteration over collections.
Classic for Loop
The three-clause form mirrors C-style loops: init; condition; post.
for i := 1; i <= 5; i++ {
fmt.Printf("i = %d\n", i)
}
Output:
i = 1
i = 2
i = 3
i = 4
i = 5
The init statement runs once before the loop. The condition is checked before each iteration. The post statement runs after each iteration.
for as while
Omit the init and post — only the condition remains. This is Go's equivalent of a while loop.
n := 3
for n > 0 {
fmt.Printf("n = %d\n", n)
n--
}
Output:
n = 3
n = 2
n = 1
Infinite Loop with break
for {} with no condition runs forever. Use break to exit.
count := 0
for {
count++
if count > 3 {
break
}
fmt.Printf("count = %d\n", count)
}
Output:
count = 1
count = 2
count = 3
range over a Slice
range yields (index, value) for each element. Use _ to discard the index when you don't need it — Go won't compile if a declared variable goes unused.
fruits := []string{"apple", "banana", "cherry"}
// Index discarded
for _, fruit := range fruits {
fmt.Println(fruit)
}
Output:
apple
banana
cherry
To capture both index and value:
for i, fruit := range fruits {
fmt.Printf("[%d] %s\n", i, fruit)
}
Output:
[0] apple
[1] banana
[2] cherry
The value yielded by range is a copy — modifying it does not change the original element.
range over a Map
range over a map yields (key, value) pairs. Map iteration order is not guaranteed — never rely on it.
ages := map[string]int{"Alice": 30, "Bob": 25, "Carol": 28}
for name, age := range ages {
fmt.Printf("%s is %d years old\n", name, age)
}
Output (order may vary):
Alice is 30 years old
Bob is 25 years old
Carol is 28 years old
continue — Skip an Iteration
continue skips the rest of the current iteration and moves to the next.
for i := 1; i <= 6; i++ {
if i%2 != 0 {
continue
}
fmt.Println(i)
}
Output:
2
4
6
range over a String
Ranging over a string yields (byteIndex, rune) pairs — it iterates Unicode code points, not bytes. For multi-byte characters, the index jumps by the rune's byte width.
for i, r := range "héllo" {
fmt.Printf("index %d: %c (U+%04X)\n", i, r, r)
}
Output:
index 0: h (U+0068)
index 1: é (U+00E9)
index 3: l (U+006C)
index 4: l (U+006C)
index 5: o (U+006F)
é is 2 bytes in UTF-8, so the index jumps from 1 to 3. Use a plain index loop if you need raw bytes.
range over an Integer (Go 1.22+)
Since Go 1.22, you can range directly over an integer to get values 0 through n-1.
for i := range 5 {
fmt.Printf("i = %d\n", i)
}
Output:
i = 0
i = 1
i = 2
i = 3
i = 4
This is shorthand for for i := 0; i < n; i++.
Labeled break
Labels let you break out of an outer loop directly from inside a nested one, without extra boolean flags.
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer
}
fmt.Printf("i=%d j=%d\n", i, j)
}
}
fmt.Println("Done.")
Output:
i=0 j=0
i=0 j=1
i=0 j=2
i=1 j=0
Done.
continue label works the same way — it skips to the next iteration of the named outer loop.
Key Takeaways
- One loop keyword:
forcovers classic, while-style, and infinite loops — there is nowhileorforeach - Blank identifier
_: Use_to discard anyrangevalue you don't need; Go won't compile with an unused variable - Range returns a copy: Modifying the
rangevalue variable does not affect the original collection - Map order is random: Never rely on the iteration order of a
rangeover a map - String range yields runes: Use
rangefor Unicode-safe iteration; use a byte index loop for raw bytes - Labeled break/continue: Target an outer loop by name to avoid complex boolean escape flags
- Range over int (Go 1.22+):
for i := range nis idiomatic shorthand for a zero-based counter loop
Related Topics
- Conditionals —
if,switch, and control flow that often pairs with loops - Go Fundamentals — core Go programming concepts