beginner
Step 3 of 15
Control Flow and Loops
Go Programming
Control Flow and Loops
Go's control flow is intentionally simple. There is only one loop construct — the for loop — which handles all iteration patterns (traditional, while-style, infinite, and range-based). The if statement supports an initialization statement, and the switch statement does not fall through by default (eliminating a common source of bugs in C-like languages). Go also provides defer for cleanup operations, which is one of its most distinctive features.
If Statements
package main
import "fmt"
func main() {
age := 25
// Basic if-else
if age >= 18 {
fmt.Println("Adult")
} else {
fmt.Println("Minor")
}
// If with initialization statement (scoped to the if block)
if score := calculateScore(); score >= 90 {
fmt.Println("Excellent!")
} else if score >= 70 {
fmt.Println("Good")
} else {
fmt.Println("Needs improvement")
}
// score is NOT accessible here
}
func calculateScore() int { return 85 }
Switch Statement
// Switch — no fall-through by default (no break needed!)
day := "Tuesday"
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("Weekday")
case "Saturday", "Sunday":
fmt.Println("Weekend")
default:
fmt.Println("Unknown")
}
// Switch without expression (like if-elseif)
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
default:
fmt.Println("C or below")
}
// Type switch
func describe(i interface{}) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("Integer: %d", v)
case string:
return fmt.Sprintf("String: %s", v)
case bool:
return fmt.Sprintf("Boolean: %t", v)
default:
return "Unknown type"
}
}
For Loops (Go's Only Loop)
// Traditional for loop
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// While-style (just a condition)
count := 0
for count < 5 {
fmt.Println(count)
count++
}
// Infinite loop
for {
fmt.Println("Press Ctrl+C to stop")
break // Use break to exit
}
// Range over slice
fruits := []string{"apple", "banana", "cherry"}
for index, fruit := range fruits {
fmt.Printf("%d: %s
", index, fruit)
}
// Range over map
scores := map[string]int{"Alice": 95, "Bob": 87}
for name, score := range scores {
fmt.Printf("%s: %d
", name, score)
}
// Range over string (iterates runes, not bytes)
for i, ch := range "Hello 🌍" {
fmt.Printf("Index %d: %c
", i, ch)
}
// Skip index with blank identifier
for _, fruit := range fruits {
fmt.Println(fruit)
}
Defer
// defer delays execution until the surrounding function returns
func readFile(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close() // Guaranteed to run when function exits
content, err := io.ReadAll(file)
if err != nil {
return "", err
}
return string(content), nil
}
// Multiple defers execute in LIFO order (last-in, first-out)
func example() {
defer fmt.Println("First deferred")
defer fmt.Println("Second deferred")
defer fmt.Println("Third deferred")
fmt.Println("Main code")
}
// Output: Main code, Third deferred, Second deferred, First deferred
Pro tip: Use defer immediately after acquiring a resource (opening a file, acquiring a lock, starting a timer) to ensure cleanup happens regardless of how the function exits. This pattern eliminates an entire class of resource leak bugs.
Key Takeaways
- Go has only one loop construct —
for— which handles traditional, while-style, infinite, and range-based iteration. - Switch does not fall through by default; no
breakneeded (usefallthroughexplicitly if needed). - The
ifstatement supports an initialization statement:if x := compute(); x > 0 { }. - Use
deferfor cleanup operations — it runs when the enclosing function returns, in LIFO order. - Use
rangeto iterate over slices, maps, strings, and channels with clean syntax.