03 · Control Flow¶
🎥 Video walkthrough¶
if / else — no parentheses required¶
package main
import "fmt"
func main() {
score := 82
if score >= 90 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else {
fmt.Println("C or below")
}
}
Braces {} are mandatory in Go, even for single-statement bodies —
there is no one-line if without braces.
if with an initializer statement¶
A very common Go idiom: run a statement, then check its result, scoped only
to the if/else:
package main
import (
"fmt"
"strconv"
)
func main() {
if value, err := strconv.Atoi("42"); err == nil {
fmt.Println("parsed:", value)
} else {
fmt.Println("parse failed:", err)
}
// value and err do not exist out here
}
This pattern shows up constantly with functions that return (result, error)
— see Module 8.
for — Go's only loop keyword¶
Go has exactly one looping construct, for, which covers every case other
languages split across for/while/do-while:
package main
import "fmt"
func main() {
// Classic three-part for
for i := 0; i < 5; i++ {
fmt.Println("count:", i)
}
// "while" loop -- condition only
n := 5
for n > 0 {
fmt.Println("n is", n)
n--
}
// Infinite loop -- break to exit
total := 0
for {
total++
if total == 3 {
break
}
}
fmt.Println("total:", total)
// range over a slice: index, value
fruits := []string{"apple", "banana", "cherry"}
for i, fruit := range fruits {
fmt.Println(i, fruit)
}
// range when you only want values
for _, fruit := range fruits {
fmt.Println("fruit:", fruit)
}
}
continue and labeled loops¶
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i%2 != 0 {
continue // skip odd numbers
}
fmt.Println(i)
}
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer // continue the OUTER loop
}
fmt.Println(i, j)
}
}
}
switch — no automatic fallthrough¶
Unlike C or Java, a Go switch case does not fall through by default —
each case breaks automatically. Use the fallthrough keyword to opt in.
package main
import "fmt"
func main() {
day := "Tue"
switch day {
case "Sat", "Sun":
fmt.Println("Weekend")
case "Mon", "Tue", "Wed", "Thu", "Fri":
fmt.Println("Weekday")
default:
fmt.Println("Unknown")
}
// switch with no expression -- reads like an if/else chain
score := 82
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
default:
fmt.Println("C or below")
}
// switch with an initializer, same idea as if
switch x := 2; x {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
fallthrough // explicitly fall into the next case
case 3:
fmt.Println("three (reached via fallthrough or directly)")
}
}
How It Actually Works¶
Go's switch doesn't fall through by default because the compiler generates a jump
table (or a chain of compares for small/sparse cases) that jumps straight to the
matching case and then to the end of the switch — falling through would require an
explicit extra jump instruction, which is exactly what the fallthrough keyword
inserts when you ask for it. A for loop with no clauses (for { }) compiles to an
unconditional backward jump — there's no hidden condition check, which is why it's
the idiomatic infinite loop instead of for true {} (both compile the same way, but
the former reads as "obviously infinite" to the compiler's dead-code and unreachable
code analysis too). The classic Go 1.21-and-earlier loop-variable-capture bug
(closures inside a for range all seeing the last value) happened because the loop
variable was one stack slot reused every iteration; each closure captured a pointer
to that slot, not a copy of the value at capture time. Go 1.22 changed the spec so
each iteration gets its own variable instance, which is why the fix is "upgrade Go,"
not "learn a workaround."
Cheat sheet¶
| Construct | Example |
|---|---|
| If | if x > 0 { ... } |
| If/else if/else | if ... else if ... else { ... } |
| If with initializer | if v, err := f(); err == nil { ... } |
| Classic for | for i := 0; i < n; i++ { ... } |
| While-style | for cond { ... } |
| Infinite | for { ... break } |
| Range (index, value) | for i, v := range slice { ... } |
| Range (value only) | for _, v := range slice { ... } |
| Switch | switch x { case 1: ...; default: ... } |
| Switch, no expression | switch { case cond: ... } |
🔀 See this in another language¶
Exercise¶
Write a program that loops from 1 to 30 and, using switch with no
expression, prints "FizzBuzz" for multiples of 15, "Fizz" for multiples
of 3, "Buzz" for multiples of 5, and the number itself otherwise. Then add
a second loop using for with range over a slice of your favorite foods
that skips (continue) any name shorter than 4 characters.