CHAPTER 10 · CORE LANGUAGE

Loops

Go has one loop keyword: for. There's no while, no do…while, no repeat. The single for just wears four different hats depending on how you use it.

Learning objectives

  • Recognize all four forms of for: three-part, condition-only, infinite, and range.
  • Use break and continue (and labels) effectively.
  • Iterate over slices, maps, strings, channels, and (in Go 1.22+) integers.
  • Avoid the classic loop-variable-in-closure pitfall.

Classic three-part for

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

Three pieces separated by semicolons:

  1. Init: runs once before the loop.
  2. Condition: checked before each iteration. false ends the loop.
  3. Post: runs after each iteration.

Any of the three can be omitted.

While equivalent

Drop init and post, keep only the condition:

i := 0
for i < 5 {
    fmt.Println(i)
    i++
}

This is how you write a "while" in Go.

Infinite loops

Drop the condition entirely:

for {
    // runs forever until break or return
}

Idiomatic for servers, poll loops, and goroutines that run for the program's lifetime. You exit with break, return, or by letting the process end.

break & continue

package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        if i == 5 {
            break      // exits the loop
        }
        if i%2 == 0 {
            continue   // skips to the next iteration
        }
        fmt.Println(i)
    }
}

This prints 1 and 3. When i reaches 5, break exits the loop entirely.

range

range iterates over almost every built-in sequence. It yields index + value for ordered containers, and key + value for maps.

Slices & arrays

nums := []int{10, 20, 30}
for i, v := range nums {
    fmt.Println(i, v)
}

Maps (random order!)

ages := map[string]int{"Alice": 30, "Bob": 25}
for name, age := range ages {
    fmt.Println(name, age)
}
!
Map iteration order is randomized Go deliberately randomizes map iteration order to keep you from depending on it. If you need a stable order, collect the keys into a slice and sort them first.

Strings (rune-aware)

for i, r := range "café" {
    fmt.Printf("byte %d = %q\n", i, r)
}

See Chapter 7: range over a string yields runes, not bytes.

Channels

for v := range ch {
    fmt.Println(v)   // ends when the channel is closed
}

More on channels in Chapter 25.

Discarding the index or value

for _, v := range nums { ... }     // only values
for i := range nums { ... }        // only indexes (omit the comma)
for range nums { ... }             // just iterate (Go 1.4+)

range over an int (Go 1.22+)

Since Go 1.22, you can range over an integer literally, a cleaner way to run a fixed number of iterations:

for i := range 5 {
    fmt.Println(i)    // 0, 1, 2, 3, 4
}

Equivalent to for i := 0; i < 5; i++ but shorter.

Nested & labeled loops

To break out of an outer loop from within an inner one, use a label:

package main

import "fmt"

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

outer:
    for i, row := range matrix {
        for j, v := range row {
            if v == 5 {
                fmt.Println("found at", i, j)
                break outer        // breaks the OUTER loop
            }
        }
    }
}

The label goes on its own line, ending with a colon, just before the loop it names. continue LABEL works the same way.

Common patterns & pitfalls

Classic loop-variable-in-closure (fixed in Go 1.22)

// In Go 1.21 and earlier, this prints "3 3 3", surprise!
// In Go 1.22+, it prints "0 1 2", per-iteration variables.
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i)
    }()
}
i
Loop variables are scoped per iteration (Go 1.22+) Before Go 1.22, loop variables were shared across iterations, a classic source of bugs, especially with goroutines and closures. Modern Go fixes this. If you're on an older version, capture the value: i := i inside the loop body, then use that.

Do-while pattern

Go has no do-while. Reach for an infinite loop with a conditional break:

for {
    answer := prompt()
    if isValid(answer) {
        break
    }
}

Countdown

for i := 5; i > 0; i-- {
    fmt.Println(i)
}
fmt.Println("lift-off!")

Check your understanding

Practice exercises

EXERCISE 1

Sum of squares

Write a program that computes the sum of squares from 1 to 10 (that is, 1² + 2² + 3² + … + 10²). Print the result.

Show one possible solution
package main

import "fmt"

func main() {
    sum := 0
    for i := 1; i <= 10; i++ {
        sum += i * i
    }
    fmt.Println(sum)   // 385
}
EXERCISE 2

Stable map iteration

Given a map[string]int, print its entries sorted by key. Use sort.Strings from the sort package.

Show one possible solution
package main

import (
    "fmt"
    "sort"
)

func main() {
    m := map[string]int{
        "cherry": 3, "apple": 1, "banana": 2,
    }

    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])
    }
}

This is the idiomatic way to get deterministic output from a map.

Further reading

One keyword, every shape of loop.