← thecodex.expert · The Codex Family of Knowledge
Go

Control Flow

One loop keyword covers every case other languages split into for, while, and do-while — and if statements never need parentheses.

Go 1.26 One loop keyword Last verified:
Where this bites you

You write a function to apply a 10% price rise across a slice of products: for _, item := range items { item.Price *= 1.10 }. It compiles clean, runs without error, and does absolutely nothing — print the slice afterward and every price is unchanged. There's no crash to point at, which is exactly why this one survives into production.

Gorangecopy.go
for _, it := range items {
    it.Price *= 1.10   // mutates a COPY of the element, not items itself
}
fmt.Println(items)   // unchanged — every price is exactly what it was

The fix is in the last for variant below: range hands you a copy of each value, so to change the original you index back into the slice instead.

Canonical Definition

Go simplifies control flow down to three keywords: if, for, and switch. There is no while or do-while — for alone covers counted loops, condition-only loops, and infinite loops, depending on which clauses you omit. Parentheses around conditions are not just optional but idiomatically omitted; braces are mandatory even for single-statement bodies.

for: Go's only loop

The same for keyword handles every loop shape by simply omitting parts of its three clauses (init; condition; post):

Goloops.go
for i := 0; i < 5; i++ {      // classic counted loop
    fmt.Println(i)
}

n := 0
for n < 3 {                    // condition only — this IS Go's "while"
    n++
}

for {                           // no clauses — infinite loop
    if n > 10 { break }
    n++
}

for i, v := range []string{"a", "b", "c"} {  // range over a slice
    fmt.Println(i, v)          // i = index, v = value
}

That last form is also the fix for the price-rise bug at the top of this page. v (or it) in a range clause is always a fresh copy of the element; to mutate the original you range for the index and write back through it:

Gorangecopy_fix.go
for i := range items {
    items[i].Price *= 1.10   // indexes into the slice itself, not a copy
}
fmt.Println(items)   // every price raised 10%, for real this time

if statements and the init shortcut

if never uses parentheses around its condition, and can carry an optional initializer statement before the condition, scoped only to the if/else chain — a pattern used constantly for error handling:

Goifstatement.go
if err := doSomething(); err != nil {
    return err   // err is scoped to this if/else block only
}

if x := compute(); x > 10 {
    fmt.Println("big")
} else if x > 0 {
    fmt.Println("small")
} else {
    fmt.Println("non-positive")
}

switch without fallthrough

Go's switch breaks from the case automatically after each match — the opposite default of C or Java, where you must remember break. Falling through to the next case requires the explicit fallthrough keyword. A switch with no expression at all acts as a clean chain of if/else conditions, and switches can match multiple values or types in one case.

Goswitchstatement.go
switch day {
case "Sat", "Sun":
    fmt.Println("weekend")
default:
    fmt.Println("weekday")
}

switch {                    // no expression: acts like if/else if/else
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B")
default:
    fmt.Println("C or below")
}

Sources

1
The Go Authors. The Go Programming Language Specification, "For statements" and "Switch statements", go.dev/ref/spec.