Updated Aug 7, 2026

Control Flow

📋 Jump to Takeaways

🎁 Go has no while keyword, no do...while, no forEach — yet its single for loop can iterate over anything. How does one keyword replace four?

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

🎁 Next up: Go has no classes — but it still has methods, encapsulation, and composition. Let's see how structs make that possible.

🚀 Ready to run?

Complete runnable examples for this lesson.

📝 Ready to test your knowledge?

Answer the quiz below to mark this lesson complete.

Spot something off? Report an issue
© 2026 ByteLearn.dev. Free courses for developers. · Privacy