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)
}
}