Control Flow

📋 Jump to Takeaways

Go keeps control flow simple. if doesn't need parentheses, switch breaks automatically, and there's only one loop keyword: for. No while, no do...while, no forEach. Fewer tools, each one doing its job perfectly.

if/else

Go's if statements don't need parentheses around the condition.

x := 10
if x > 5 {
    fmt.Println("big") // big
}

You can include an init statement before the condition. The variable is scoped to the if/else block.

if err := doSomething(); err != nil {
    fmt.Println(err)
} else {
    fmt.Println("success")
}
// err is not accessible here

switch

Go's switch doesn't need break. Each case breaks automatically. Use fallthrough if you want to fall into the next case.

day := "Monday"
switch day {
case "Monday":
    fmt.Println("Start of week") // Start of week
case "Friday":
    fmt.Println("Almost weekend")
default:
    fmt.Println("Midweek")
}

A single case can match multiple values, separated by commas.

switch day {
case "Saturday", "Sunday":
    fmt.Println("Weekend")
default:
    fmt.Println("Weekday")
}

An expression-less switch acts like a clean if/else chain.

score := 85
switch {
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B") // B
default:
    fmt.Println("C")
}

Go has a special construct called a type switch that checks the concrete type of an interface value.

var val interface{} = "hello"
switch v := val.(type) {
case string:
    fmt.Println("string:", v) // string: hello
case int:
    fmt.Println("int:", v)
}

for Loop

for is the only loop in Go. It covers all looping patterns.

Classic three-component form:

for i := 0; i < 5; i++ {
    fmt.Println(i) // 0, 1, 2, 3, 4
}

While-style, just a condition:

n := 1
for n < 100 {
    n *= 2
}
fmt.Println(n) // 128

Range over an integer (Go 1.22+). Iterates from 0 to n-1:

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

If you don't need the index, drop it:

for range 3 {
    fmt.Println("hello") // prints 3 times
}

Infinite loop. Use break to exit:

for {
    fmt.Println("runs once")
    break
}

range

range iterates over slices, maps, strings, and channels.

nums := []int{10, 20, 30}
for i, v := range nums {
    fmt.Println(i, v) // 0 10, 1 20, 2 30
}

Use _ to discard the index or value. Go requires you to use every variable you declare, so _ is the blank identifier that tells the compiler "I don't need this."

for _, v := range nums {
    fmt.Println(v) // 10, 20, 30
}

Ranging over a map gives key-value pairs in random order.

m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
    fmt.Println(k, v)
}

Ranging over a string gives runes, not bytes.

for i, ch := range "Go!" {
    fmt.Printf("%d: %c\n", i, ch) // 0: G, 1: o, 2: !
}

break, continue, and Labels

continue skips to the next iteration. break exits the loop.

for i := 0; i < 5; i++ {
    if i == 2 {
        continue
    }
    fmt.Println(i) // 0, 1, 3, 4
}

Labels let you break or continue an outer loop from inside a nested loop.

outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            continue outer
        }
        fmt.Println(i, j) // 0 0, 1 0, 2 0
    }
}

Key Takeaways

  • if statements can include an init statement — great for error checks
  • switch cases break automatically; no break needed
  • for is the only loop — it handles classic, while-style, and infinite loops
  • range works on slices, maps, strings, and channels
  • Labels let you control nested loops with break and continue

📝 Ready to test your knowledge?

Answer the quiz below to mark this lesson complete.

© 2026 ByteLearn.dev. Free courses for developers. · Privacy