On this page

Control Flow

12 min read TextCh. 1 — Go Fundamentals

Control Flow in Go: Simplified and Powerful

Control flow determines which instructions execute, how many times, and under what conditions. Go makes deliberate design choices to simplify control structures: there is one loop (for), if conditions have no parentheses, and switch requires no break. Less syntax, fewer errors.

`if` and `else`: No Parentheses

The first difference you will notice coming from C, Java, or JavaScript is that conditions in Go have no parentheses. Curly braces {} are always required — Go does not allow single-line forms without braces:

temperature := 25

if temperature > 30 {
    fmt.Println("Hot")
} else if temperature > 20 {
    fmt.Println("Pleasant")
} else {
    fmt.Println("Cold")
}

The if Initialization Statement

Go allows declaring a variable inside the if that only exists within the if/else scope. This pattern is very common when a result is only needed inside the conditional:

// n only exists inside the if/else
if n := calculateValue(); n > 100 {
    fmt.Println("High value:", n)
} else {
    fmt.Println("Normal value:", n)
}
// n does not exist here

// Very common pattern with errors
if err := doSomething(); err != nil {
    fmt.Println("Error:", err)
    return
}

This pattern keeps code clean: the variable is available exactly where it is needed and does not pollute the surrounding scope.

`switch`: Elegant and Safe

Go's switch is more powerful and safer than C's or Java's:

Basic switch

day := 3  // Wednesday

switch day {
case 1:
    fmt.Println("Monday")
case 2:
    fmt.Println("Tuesday")
case 3:
    fmt.Println("Wednesday")
case 4:
    fmt.Println("Thursday")
case 5:
    fmt.Println("Friday")
default:
    fmt.Println("Weekend")
}

Multiple values per case

switch day {
case 1, 2, 3, 4, 5:
    fmt.Println("Weekday")
case 6, 7:
    fmt.Println("Weekend")
}

Switch with no expression (like if-else-if)

When you omit the comparison expression, switch evaluates boolean conditions in each case:

hour := 14

switch {
case hour < 12:
    fmt.Println("Good morning")
case hour < 18:
    fmt.Println("Good afternoon")
default:
    fmt.Println("Good evening")
}

Switch with initialization

switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("macOS")
case "linux":
    fmt.Println("Linux")
default:
    fmt.Printf("Other: %s\n", os)
}

Explicit `fallthrough`

If you need C-style fall-through behavior, use it explicitly:

n := 3
switch n {
case 3:
    fmt.Println("Three")
    fallthrough  // execute the next case too
case 2:
    fmt.Println("Two or more")
case 1:
    fmt.Println("One")
}
// Prints: Three
//         Two or more

`for`: Go's Only Loop

Go made the radical decision to have a single loop type: for. But for has multiple forms that cover every use case.

Form 1: Classic (init; condition; post)

// Sum 1 to 100
sum := 0
for i := 1; i <= 100; i++ {
    sum += i
}
fmt.Println("Sum:", sum)  // 5050

// Count down
for i := 10; i > 0; i-- {
    fmt.Print(i, " ")
}
fmt.Println()

// Multiple variables
for i, j := 0, 10; i < j; i, j = i+1, j-1 {
    fmt.Printf("i=%d, j=%d\n", i, j)
}

Form 2: Conditional (equivalent to `while`)

entry := 0
for entry < 5 {
    entry++
    fmt.Println("Processing:", entry)
}

// "while true" loop with internal break
attempts := 0
for {
    attempts++
    if attempts >= 3 {
        break
    }
}

Form 3: Infinite Loop

// Server that runs forever (common in goroutines)
for {
    // process work...
    // use break or return to exit
}

`range`: Iterating Over Collections

range is the idiomatic Go way to iterate over arrays, slices, maps, strings, and channels:

// Over a slice: index and value
numbers := []int{10, 20, 30, 40, 50}
for i, n := range numbers {
    fmt.Printf("numbers[%d] = %d\n", i, n)
}

// Index only
for i := range numbers {
    fmt.Println(i)
}

// Value only (discard index with _)
for _, n := range numbers {
    fmt.Println(n)
}

// Over a map: key and value
capitals := map[string]string{
    "Bolivia":   "Sucre",
    "Argentina": "Buenos Aires",
    "Brazil":    "Brasilia",
}
for country, capital := range capitals {
    fmt.Printf("Capital of %s: %s\n", country, capital)
}

// Over a string: byte index and rune
for i, r := range "Hello!" {
    fmt.Printf("index: %d, char: %c\n", i, r)
}

// Go 1.22+: range over integer
for i := range 5 {  // 0, 1, 2, 3, 4
    fmt.Println(i)
}

`break` and `continue`

// break: exit the loop immediately
for i := 0; i < 10; i++ {
    if i == 5 {
        break
    }
    fmt.Println(i)
}
// Prints: 0 1 2 3 4

// continue: skip to the next iteration
for i := 0; i < 10; i++ {
    if i%2 == 0 {
        continue  // skip even numbers
    }
    fmt.Println(i)
}
// Prints: 1 3 5 7 9

Labeled Break: Exiting Nested Loops

In Go, you can put a label on a loop and use it with break or continue to control outer loops. This avoids the need for extra control variables:

// Without labels: need an extra variable
found := false
for i := 0; i < 5 && !found; i++ {
    for j := 0; j < 5; j++ {
        if i*j > 6 {
            found = true
            break
        }
    }
}

// With labels: cleaner
outer:
for i := 0; i < 5; i++ {
    for j := 0; j < 5; j++ {
        if i*j > 6 {
            fmt.Printf("Found at (%d, %d)\n", i, j)
            break outer  // exits both loops
        }
    }
}

// Labeled continue
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            continue outer  // skip to next i
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// Prints: (0,0) (1,0) (2,0)

Summary of Idiomatic Patterns

Pattern Code
if with initialization if err := f(); err != nil { ... }
While for condition { ... }
Infinite for { ... }
Classic for for i := 0; i < n; i++ { ... }
Iterate slice for i, v := range slice { ... }
Iterate map for k, v := range m { ... }
Exit nested loops break label

With these control structures mastered, in the next lesson we will learn Go functions: multiple return values, variadic functions, closures, and the powerful defer statement.

Go has only one loop construct: for
Go has no while or do-while. Instead, for has three forms: classic (for i := 0; i < n; i++), conditional like while (for condition), and infinite loop (for {}). This simplification makes code more uniform and predictable across any Go codebase.
switch in Go does not need break
Unlike C, Java or JavaScript, switch cases in Go do not fall through to the next case automatically. If you want that behavior, use the fallthrough keyword explicitly. This eliminates one of the most common bugs in C-style switch statements.