Learn › Go Programming

Control Flow

Learn Go's control flow statements including if, for, switch, defer, and range.

If Statements

Go's if statements do not require parentheses around the condition but do require braces around the body. A unique feature of Go is the ability to include a short initialization statement before the condition, separated by a semicolon. Variables declared in this initialization statement are scoped to the if-else block. This pattern is especially common when checking error returns from function calls.

package main

import (
    "fmt"
    "os"
)

func main() {
    x := 42

    if x > 0 {
        fmt.Println("positive")
    } else if x < 0 {
        fmt.Println("negative")
    } else {
        fmt.Println("zero")
    }

    // If with initialization statement
    if file, err := os.Open("test.txt"); err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Opened:", file.Name())
        file.Close()
    }

    // Common pattern: early return on error
    if err := doSomething(); err != nil {
        fmt.Println("Failed:", err)
    }
}

func doSomething() error {
    return nil
}

For Loops

Go has only one looping construct: the 'for' loop, but it can be used in several different ways. The traditional three-component form works like C-style for loops. Omitting the init and post statements creates a while-equivalent loop. Omitting all components creates an infinite loop that must be broken with 'break' or 'return'. The 'continue' keyword skips to the next iteration, and 'break' exits the loop entirely.

package main

import "fmt"

func main() {
    // Traditional for loop
    for i := 0; i < 5; i++ {
        fmt.Print(i, " ")
    }
    fmt.Println()

    // While-style loop
    n := 1
    for n < 100 {
        n *= 2
    }
    fmt.Println("n:", n)

    // Infinite loop with break
    count := 0
    for {
        if count >= 3 {
            break
        }
        fmt.Println("count:", count)
        count++
    }

    // Continue to skip iterations
    for i := 0; i < 10; i++ {
        if i%2 == 0 {
            continue
        }
        fmt.Print(i, " ") // Prints odd numbers
    }
    fmt.Println()
}

Switch Statements

Go's switch statement is more powerful and flexible than in most languages. Cases do not fall through by default, eliminating a common source of bugs — you must explicitly use 'fallthrough' if you want that behavior. Switch cases can use expressions, not just constants, and you can switch on any comparable type. A switch without a condition acts as a cleaner alternative to long if-else chains.

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    // Basic switch
    os := runtime.GOOS
    switch os {
    case "darwin":
        fmt.Println("macOS")
    case "linux":
        fmt.Println("Linux")
    default:
        fmt.Println("Other:", os)
    }

    // Switch with no condition (like if-else)
    hour := time.Now().Hour()
    switch {
    case hour < 12:
        fmt.Println("Good morning")
    case hour < 17:
        fmt.Println("Good afternoon")
    default:
        fmt.Println("Good evening")
    }

    // Multiple values in a case
    day := time.Now().Weekday()
    switch day {
    case time.Saturday, time.Sunday:
        fmt.Println("Weekend!")
    default:
        fmt.Println("Weekday")
    }
}

Defer and Range

The 'defer' keyword schedules a function call to be executed when the surrounding function returns, regardless of how it returns. Deferred calls are pushed onto a stack and executed in last-in-first-out order. This is commonly used for cleanup tasks like closing files, releasing locks, or flushing buffers. The 'range' keyword provides a convenient way to iterate over arrays, slices, maps, strings, and channels.

package main

import "fmt"

func main() {
    // Defer executes in LIFO order
    fmt.Println("counting:")
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
    // Prints: 2, 1, 0 after "counting:"

    // Range over a slice
    fruits := []string{"apple", "banana", "cherry"}
    for index, value := range fruits {
        fmt.Printf("%d: %s\n", index, value)
    }

    // Range over a map
    ages := map[string]int{"Alice": 30, "Bob": 25}
    for name, age := range ages {
        fmt.Printf("%s is %d\n", name, age)
    }

    // Range over a string (iterates runes)
    for i, ch := range "Hello" {
        fmt.Printf("%d: %c\n", i, ch)
    }
}

← Functions & Methods · Arrays, Slices & Maps →