Go, chapter by chapter · Lesson 4 of 62 · about 35 min
Control structures
for, if and switch: Go's only three control statements, how each form runs step by step, and the patterns you'll use in every algorithm.
On this page · 8 sections
for: the only loop#
Form 1: the counting loop
for i := 1; i <= 3; i++ {
fmt.Println(i)
}It has three parts separated by semicolons. Here is exactly what happens:
| Step | Action | i | Output |
|---|---|---|---|
| 1 | init: i := 1 | 1 | |
| 2 | condition i <= 3? yes, run body | 1 | 1 |
| 3 | post: i++ | 2 | |
| 4 | condition? yes, run body | 2 | 2 |
| 5 | post | 3 | |
| 6 | condition? yes, run body | 3 | 3 |
| 7 | post | 4 | |
| 8 | condition? no, exit | 4 |
The condition is checked before each iteration, so a loop can run zero times.
Form 2: the while loop
Drop the init and post parts and keep only the condition:
n := 100
steps := 0
for n > 1 {
n /= 2
steps++
}Form 3: the infinite loop
for { … } runs until something inside it calls break or return. Servers and event loops use this form.
Form 4: range
range walks over a collection. What you get depends on what you range over:
| Range over | You get |
|---|---|
| slice or array | index, value |
| string | byte index, rune |
| map | key, value (random order) |
| channel | each value until the channel is closed |
integer n (Go 1.22+) | 0, 1, …, n-1 |
Drop what you do not need: for i := range s gives indexes only, for _, v := range s gives values only.
break, continue and labels
break leaves the loop; continue skips to the next iteration. In nested loops, both apply to the innermost loop unless you name an outer one with a label:
outer:
for r := range grid {
for c := range grid[r] {
if grid[r][c] == target {
fmt.Println("found at", r, c)
break outer // leaves both loops
}
}
}if#
if x > 0 {
fmt.Println("positive")
} else if x < 0 {
fmt.Println("negative")
} else {
fmt.Println("zero")
}No parentheses around the condition, braces always required, and the condition must be a bool (Go will not treat 0 or "" as false).
An if can start with a short statement whose variables live only inside the if/else chain:
if n, err := strconv.Atoi(s); err != nil {
fmt.Println("not a number")
} else {
fmt.Println(n * 2)
}switch#
switch compares a value against several cases. The first match runs, and then the switch ends; there is no fall-through.
switch day {
case "sat", "sun":
fmt.Println("weekend")
case "fri":
fmt.Println("almost")
default:
fmt.Println("workday")
}A switch with no value is a clean replacement for long if-else chains. Each case is a condition, checked top to bottom:
switch {
case score >= 90:
grade = "A"
case score >= 80:
grade = "B"
default:
grade = "C"
}More examples#
The book's exercises: FizzBuzz and multiples of three
package main
import (
"fmt"
"strings"
)
func main() {
// Print 1 to 15; multiples of 3 say Fizz, of 5 say Buzz, of both say FizzBuzz.
var out []string
for i := 1; i <= 15; i++ {
switch {
case i%15 == 0:
out = append(out, "FizzBuzz")
case i%3 == 0:
out = append(out, "Fizz")
case i%5 == 0:
out = append(out, "Buzz")
default:
out = append(out, fmt.Sprint(i))
}
}
fmt.Println(strings.Join(out, " "))
// The numbers from 1 to 100 that are divisible by 3, counted and summed.
count, sum := 0, 0
for i := 3; i <= 100; i += 3 { // step by 3 instead of testing every number
count++
sum += i
}
fmt.Println(count, "multiples of 3, sum", sum)
// Even or odd for 1..10, the chapter's warm-up.
for i := range 10 {
n := i + 1
parity := "odd"
if n%2 == 0 {
parity = "even"
}
fmt.Print(n, ":", parity, " ")
}
fmt.Println()
}1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
33 multiples of 3, sum 1683
1:odd 2:even 3:odd 4:even 5:odd 6:even 7:odd 8:even 9:odd 10:evenswitch details: fallthrough, no condition, and init statements
package main
import (
"fmt"
"time"
)
func describe(n int) string {
switch {
case n < 0:
return "negative"
case n == 0:
return "zero"
case n%2 == 0:
return "positive even"
default:
return "positive odd"
}
}
func main() {
for _, n := range []int{-3, 0, 4, 7} {
fmt.Printf("%d is %s\n", n, describe(n))
}
// Cases do not fall through by default. fallthrough opts in, unconditionally.
switch level := 2; level {
case 1:
fmt.Println("level 1 setup")
fallthrough
case 2:
fmt.Println("level 2 setup")
fallthrough
case 3:
fmt.Println("level 3 setup")
}
switch d := time.Date(2026, 10, 17, 0, 0, 0, 0, time.UTC).Weekday(); d {
case time.Saturday, time.Sunday:
fmt.Println(d, "is a weekend")
default:
fmt.Println(d, "is a weekday")
}
}-3 is negative
0 is zero
4 is positive even
7 is positive odd
level 2 setup
level 3 setup
Saturday is a weekendLoops in modern Go: ranging over integers and per-iteration variables
Since Go 1.22, for i := range n counts from 0 to n-1, and each iteration of a for loop gets a fresh copy of its loop variables. Before 1.22, all the closures below would have captured the same i and printed 3 three times.
package main
import "fmt"
func main() {
for i := range 3 {
fmt.Print(i, " ")
}
fmt.Println()
var funcs []func() int
for i := 0; i < 3; i++ {
funcs = append(funcs, func() int { return i }) // each closure gets its own i (Go 1.22+)
}
for _, f := range funcs {
fmt.Print(f(), " ")
}
fmt.Println()
// Ranging over a string yields runes and their byte offsets.
for i, r := range "añb" {
fmt.Printf("%d:%c ", i, r)
}
fmt.Println()
// A labeled continue skips to the next iteration of the OUTER loop.
rows := [][]int{{1, 2, -1, 4}, {5, 6, 7}, {-2, 9}}
outer:
for r, row := range rows {
for _, v := range row {
if v < 0 {
fmt.Printf("row %d has a negative, skipping the rest of it\n", r)
continue outer
}
}
fmt.Printf("row %d is all non-negative\n", r)
}
}0 1 2
0 1 2
0:a 1:ñ 3:b
row 0 has a negative, skipping the rest of it
row 1 is all non-negative
row 2 has a negative, skipping the rest of itThe full program#
package main
import (
"fmt"
"strconv"
)
func fizzBuzz(n int) []string {
out := make([]string, 0, n)
for i := 1; i <= n; i++ {
switch {
case i%15 == 0:
out = append(out, "FizzBuzz")
case i%3 == 0:
out = append(out, "Fizz")
case i%5 == 0:
out = append(out, "Buzz")
default:
out = append(out, strconv.Itoa(i))
}
}
return out
}
func classify(n int) string {
if n < 0 {
return "negative" // early return
}
if n%2 == 0 {
return "even"
}
return "odd"
}
func main() {
fmt.Println(fizzBuzz(15))
n, steps := 100, 0
for n > 1 {
n /= 2
steps++
}
fmt.Println("halvings:", steps)
for i := range 3 {
fmt.Print(classify(i-1), " ")
}
fmt.Println()
grid := [][]int{{1, 2, 3}, {4, 5, 6}}
outer:
for r, row := range grid {
for c, v := range row {
if v == 5 {
fmt.Println("found 5 at", r, c)
break outer
}
}
}
}[1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz]
halvings: 6
negative even odd
found 5 at 1 1Why 6 halvings? 100 → 50 → 25 → 12 → 6 → 3 → 1. That repeated halving is exactly what makes binary search fast, which you will meet in Module 2.
Summary#
foris the only loop: counting, while-style, infinite and range forms.rangegives index and value for slices, rune for strings, key and value for maps, and counts for integers.ifneeds a realbool, can declare short-lived variables, and works best with early returns.switchstops at the first match; a bareswitchreplaces if-else chains.- Labels let
breakandcontinuetarget an outer loop.
Check your understanding
Answer before scrolling back up. If you miss one, reread the matching section, then try again.
Q1.Which loop keywords does Go have?
Q2.Does a Go
switchcase fall through to the next one?Q3.
for i := range 3 { fmt.Print(i) }prints:Q4.In
if v, ok := m[k]; ok { ... }, where can you use v?
Practice in Go
Write a table test first, then the solution. On LeetCode, choose Go as the language. Open a hint only after 15 minutes stuck.
- Fizz Buzz ↗easy
Show hint
Check divisibility by 15 first, or the 3 and 5 cases will catch it.
Show hint
A loop works, but there is an O(1) formula: (high+1)/2 - low/2.
Show hint
A while-style
for n > 0loop with an if/else inside.- Multiplication gridbuild
Print a 9×9 multiplication table with aligned columns, then stop early at the first product over 50 using a labeled break.
Show hint
fmt.Printf("%4d", n)pads to width 4. Putouter:before the outer loop and writebreak outer.
Finished this lesson?
Mark it done to track your progress. It is saved in this browser only.