Go Loops
Overview
Go has exactly one looping construct: for. It replaces C's for, while, and do-while. No parentheses around the loop header, braces always required. This keeps the language minimal without sacrificing expressiveness.
C-Style for Loop
The classic loop with init, condition, and post statement. Init runs once before the loop, condition is checked before each iteration, post runs after each iteration.
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 0 1 2 3 4
// Step by 2
for j := 0; j < 10; j += 2 {
fmt.Println(j)
}
// 0 2 4 6 8
// Count down
for i := 5; i > 0; i-- {
fmt.Print(i, " ")
}
// 5 4 3 2 1
While-Style Loop
Drop the init and post — just keep the condition. Equivalent to while in other languages.
n := 1
for n < 1000 {
n *= 2
}
fmt.Println(n) // 1024
// Read lines until EOF
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
Infinite Loop
Omit the condition entirely. Use break to exit, return to exit the whole function.
for {
input := readInput()
if input == "quit" {
break
}
process(input)
}
range — Iterating Collections
range produces an index+value pair for each element. It works on slices, arrays, maps, strings, and channels. Use _ to discard either the index or the value.
Slice / Array
nums := []int{10, 20, 30, 40}
for i, v := range nums {
fmt.Printf("index %d = %d\n", i, v)
}
for _, v := range nums { fmt.Println(v) } // value only
for i := range nums { fmt.Println(i) } // index only
Map
Order is not guaranteed — randomised on each run by design.
scores := map[string]int{"Alice": 95, "Bob": 87, "Carol": 92}
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
for name := range scores { // keys only
fmt.Println(name)
}
String
range on a string yields Unicode runes (int32), not bytes. Multi-byte characters are handled correctly.
for i, ch := range "Go🚀" {
fmt.Printf("index %d: %c (%d)\n", i, ch, ch)
}
// index 0: G (71)
// index 1: o (111)
// index 2: 🚀 (128640) — one rune, but 4 bytes
Channel
range over a channel reads until the channel is closed.
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ { ch <- i }
close(ch)
}()
for v := range ch {
fmt.Println(v) // 0, 1, 2
}
continue and break
continue— skip the remaining code in the current iteration, jump to the next onebreak— exit the loop immediately, regardless of condition
// Print only odd numbers
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // skip even
}
fmt.Println(i) // 1 3 5 7 9
}
// Find first negative number
nums := []int{3, 7, -1, 4, -2}
for _, n := range nums {
if n < 0 {
fmt.Println("first negative:", n)
break // -1
}
}
Labeled Loops — Targeting Outer Loops
A label lets break or continue target an outer loop directly. This avoids awkward boolean flag variables in nested loops.
// Without labels — needs a flag
found := false
for i := 0; i < 5 && !found; i++ {
for j := 0; j < 5; j++ {
if matrix[i][j] == target {
found = true
break
}
}
}
// With labels — cleaner
outer:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if matrix[i][j] == target {
fmt.Printf("found at (%d, %d)\n", i, j)
break outer // exits both loops
}
}
}
// continue outer — skip to next i, not next j
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer
}
fmt.Printf("i=%d j=%d\n", i, j)
}
}
// i=0 j=0 / i=1 j=0 / i=2 j=0
Common Loop Patterns
Sum a slice
nums := []int{1, 2, 3, 4, 5}
sum := 0
for _, n := range nums {
sum += n
}
// sum = 15
Reverse a slice in place
s := []int{1, 2, 3, 4, 5}
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
// s = [5 4 3 2 1]
Filter into new slice
nums := []int{1, 2, 3, 4, 5, 6}
var evens []int
for _, n := range nums {
if n%2 == 0 {
evens = append(evens, n)
}
}
// evens = [2 4 6]
Find max
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
max := nums[0]
for _, n := range nums[1:] {
if n > max {
max = n
}
}
// max = 9
Collect map keys, sorted
import "sort"
m := map[string]int{"b": 2, "a": 1, "c": 3}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, m[k])
}
Gotchas
| Gotcha | Detail |
|---|---|
| Loop variable capture (Go < 1.22) | Closures inside loops capture the loop variable by reference — all closures see the final value. Use v := v to create a local copy. Go 1.22+ fixes this. |
| Map iteration order | Randomised per run — do not rely on order |
range on nil slice | Safe — iterates zero times |
range string gives runes not bytes | Multi-byte Unicode chars span multiple byte indices |
| Modifying slice while ranging | Changing elements by index is safe; append may create a new backing array |