On this page
Control Flow
Control Flow in Go: Simplified and Powerful
Control flow determines which instructions execute, how many times, and under what conditions. Go makes deliberate design choices to simplify control structures: there is one loop (for), if conditions have no parentheses, and switch requires no break. Less syntax, fewer errors.
`if` and `else`: No Parentheses
The first difference you will notice coming from C, Java, or JavaScript is that conditions in Go have no parentheses. Curly braces {} are always required — Go does not allow single-line forms without braces:
temperature := 25
if temperature > 30 {
fmt.Println("Hot")
} else if temperature > 20 {
fmt.Println("Pleasant")
} else {
fmt.Println("Cold")
}The if Initialization Statement
Go allows declaring a variable inside the if that only exists within the if/else scope. This pattern is very common when a result is only needed inside the conditional:
// n only exists inside the if/else
if n := calculateValue(); n > 100 {
fmt.Println("High value:", n)
} else {
fmt.Println("Normal value:", n)
}
// n does not exist here
// Very common pattern with errors
if err := doSomething(); err != nil {
fmt.Println("Error:", err)
return
}This pattern keeps code clean: the variable is available exactly where it is needed and does not pollute the surrounding scope.
`switch`: Elegant and Safe
Go's switch is more powerful and safer than C's or Java's:
Basic switch
day := 3 // Wednesday
switch day {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
case 3:
fmt.Println("Wednesday")
case 4:
fmt.Println("Thursday")
case 5:
fmt.Println("Friday")
default:
fmt.Println("Weekend")
}Multiple values per case
switch day {
case 1, 2, 3, 4, 5:
fmt.Println("Weekday")
case 6, 7:
fmt.Println("Weekend")
}Switch with no expression (like if-else-if)
When you omit the comparison expression, switch evaluates boolean conditions in each case:
hour := 14
switch {
case hour < 12:
fmt.Println("Good morning")
case hour < 18:
fmt.Println("Good afternoon")
default:
fmt.Println("Good evening")
}Switch with initialization
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS")
case "linux":
fmt.Println("Linux")
default:
fmt.Printf("Other: %s\n", os)
}Explicit `fallthrough`
If you need C-style fall-through behavior, use it explicitly:
n := 3
switch n {
case 3:
fmt.Println("Three")
fallthrough // execute the next case too
case 2:
fmt.Println("Two or more")
case 1:
fmt.Println("One")
}
// Prints: Three
// Two or more`for`: Go's Only Loop
Go made the radical decision to have a single loop type: for. But for has multiple forms that cover every use case.
Form 1: Classic (init; condition; post)
// Sum 1 to 100
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println("Sum:", sum) // 5050
// Count down
for i := 10; i > 0; i-- {
fmt.Print(i, " ")
}
fmt.Println()
// Multiple variables
for i, j := 0, 10; i < j; i, j = i+1, j-1 {
fmt.Printf("i=%d, j=%d\n", i, j)
}Form 2: Conditional (equivalent to `while`)
entry := 0
for entry < 5 {
entry++
fmt.Println("Processing:", entry)
}
// "while true" loop with internal break
attempts := 0
for {
attempts++
if attempts >= 3 {
break
}
}Form 3: Infinite Loop
// Server that runs forever (common in goroutines)
for {
// process work...
// use break or return to exit
}`range`: Iterating Over Collections
range is the idiomatic Go way to iterate over arrays, slices, maps, strings, and channels:
// Over a slice: index and value
numbers := []int{10, 20, 30, 40, 50}
for i, n := range numbers {
fmt.Printf("numbers[%d] = %d\n", i, n)
}
// Index only
for i := range numbers {
fmt.Println(i)
}
// Value only (discard index with _)
for _, n := range numbers {
fmt.Println(n)
}
// Over a map: key and value
capitals := map[string]string{
"Bolivia": "Sucre",
"Argentina": "Buenos Aires",
"Brazil": "Brasilia",
}
for country, capital := range capitals {
fmt.Printf("Capital of %s: %s\n", country, capital)
}
// Over a string: byte index and rune
for i, r := range "Hello!" {
fmt.Printf("index: %d, char: %c\n", i, r)
}
// Go 1.22+: range over integer
for i := range 5 { // 0, 1, 2, 3, 4
fmt.Println(i)
}`break` and `continue`
// break: exit the loop immediately
for i := 0; i < 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
// Prints: 0 1 2 3 4
// continue: skip to the next iteration
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // skip even numbers
}
fmt.Println(i)
}
// Prints: 1 3 5 7 9Labeled Break: Exiting Nested Loops
In Go, you can put a label on a loop and use it with break or continue to control outer loops. This avoids the need for extra control variables:
// Without labels: need an extra variable
found := false
for i := 0; i < 5 && !found; i++ {
for j := 0; j < 5; j++ {
if i*j > 6 {
found = true
break
}
}
}
// With labels: cleaner
outer:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i*j > 6 {
fmt.Printf("Found at (%d, %d)\n", i, j)
break outer // exits both loops
}
}
}
// Labeled continue
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer // skip to next i
}
fmt.Printf("(%d,%d) ", i, j)
}
}
// Prints: (0,0) (1,0) (2,0)Summary of Idiomatic Patterns
| Pattern | Code |
|---|---|
| if with initialization | if err := f(); err != nil { ... } |
| While | for condition { ... } |
| Infinite | for { ... } |
| Classic for | for i := 0; i < n; i++ { ... } |
| Iterate slice | for i, v := range slice { ... } |
| Iterate map | for k, v := range m { ... } |
| Exit nested loops | break label |
With these control structures mastered, in the next lesson we will learn Go functions: multiple return values, variadic functions, closures, and the powerful defer statement.
Sign in to track your progress