for Loops

Go has exactly one loop keyword: for. With different parts filled in, it covers the counting loop, the while loop and the infinite loop of other languages — and with range (next lesson) it walks over collections. This lesson covers each form, counting with range over an integer, and how loop variables behave.

The three-part loop

The classic form has an init statement, a condition and a post statement, separated by semicolons and with no parentheses:

GO
package main

import "fmt"

func main() {
	for i := 1; i <= 5; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println()

	for i := 10; i > 0; i -= 3 {
		fmt.Print(i, " ")
	}
	fmt.Println()
}
Output
1 2 3 4 5
10 7 4 1

The init statement runs once; the condition is checked before every pass; the post statement runs after every pass. A variable declared in the init statement belongs to the loop and disappears when it ends.

Counting with range

To count from 0 up to (but not including) n, the shortest form is range n:

GO
package main

import "fmt"

func main() {
	for i := range 4 {
		fmt.Println("row", i)
	}

	for range 3 { // when you don't need the counter
		fmt.Print("hip ")
	}
	fmt.Println("hooray!")
}
Output
row 0
row 1
row 2
row 3
hip hip hip hooray!

The while-style loop

Keep only the condition and for behaves like while in other languages:

GO
package main

import "fmt"

func main() {
	balance := 1000.0
	years := 0
	for balance < 2000 {
		balance *= 1.07 // 7% interest per year
		years++
	}
	fmt.Printf("doubled after %d years: %.2f\n", years, balance)
}
Output
doubled after 11 years: 2104.85

The infinite loop

Leave out everything and the loop runs until something inside it leaves — break, return, or the program exiting. This is common for servers, game loops and "keep asking until valid" logic:

GO
package main

import "fmt"

func main() {
	n := 27
	steps := 0
	for {
		if n == 1 {
			break
		}
		if n%2 == 0 {
			n /= 2
		} else {
			n = 3*n + 1
		}
		steps++
	}
	fmt.Println("reached 1 after", steps, "steps")
}
Output
reached 1 after 111 steps

continue

continue skips the rest of the current pass and moves on to the next one:

GO
package main

import "fmt"

func main() {
	for i := range 10 {
		if i%3 != 0 {
			continue
		}
		fmt.Print(i, " ")
	}
	fmt.Println()
}
Output
0 3 6 9

Lesson 18 covers break and continue in nested loops.

Nested loops

GO
package main

import "fmt"

func main() {
	for row := 1; row <= 4; row++ {
		for col := 1; col <= 4; col++ {
			fmt.Printf("%4d", row*col)
		}
		fmt.Println()
	}
}
Output
   1   2   3   4
   2   4   6   8
   3   6   9  12
   4   8  12  16

Each pass gets a fresh variable

Since Go 1.22, every iteration of a loop has its own copy of the loop variable. That matters when a function created inside the loop remembers the variable (a closure, lesson 33) — each one sees the value from its own pass:

GO
package main

import "fmt"

func main() {
	var printers []func()
	for i := range 3 {
		printers = append(printers, func() { fmt.Print(i, " ") })
	}
	for _, p := range printers {
		p()
	}
	fmt.Println()
}
Output
0 1 2

In code written for Go versions before 1.22, all three functions shared one variable and printed 3 3 3. You may still see the old workaround i := i inside loops; with a modern go line in go.mod it's no longer needed, and go fix removes it.

Try it yourself

Print the FizzBuzz sequence from 1 to 15: "Fizz" for multiples of 3, "Buzz" for multiples of 5, "FizzBuzz" for both. Then try rewriting it with a switch.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
Next: looping over collections with range.