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:
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() }
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:
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!") }
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:
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) }
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:
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") }
reached 1 after 111 steps
continue
continue skips the rest of the current pass and moves on to the next one:
package main import "fmt" func main() { for i := range 10 { if i%3 != 0 { continue } fmt.Print(i, " ") } fmt.Println() }
0 3 6 9
Lesson 18 covers break and continue in nested loops.
Nested loops
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() } }
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:
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() }
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.
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
range.