Go Control Flow — if else for switch and defer Explained
In this tutorial, you will learn about Go Control Flow. We cover key concepts, practical examples, and best practices to help you master this topic.
Go control flow includes if/else with initialization statements, for as the only loop keyword (no while/until), switch with expressionless and type-switch forms, and defer for guaranteed cleanup.
What You'll Learn
- If/else with initialization statements
- For loops in all forms
- Switch statements with multiple cases
- Defer for cleanup operations
Why It Matters
Go's control flow is minimal but expressive. Docker uses for loops for container operations. Kubernetes controllers use switch statements for resource reconciliation. Doda Browser uses defer for resource cleanup. Mastering these constructs lets you write idiomatic Go.
Real-World Use
An HTTP handler checks for errors with if, iterates over data with for, handles different content types with switch, and defers response writing. A CLI tool loops over arguments, parses flags, and handles subcommands with switch.
flowchart LR
A["Control Flow"] --> B["if/else"]
B --> C["for loops"]
C --> D["switch"]
D --> E["defer"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
If/Else
package main
import "fmt"
func main() {
age := 18
if age >= 18 {
fmt.Println("Adult")
} else {
fmt.Println("Minor")
}
}
If with Initialization
if score := 85; score >= 90 {
fmt.Println("Grade: A")
} else if score >= 80 {
fmt.Println("Grade: B")
} else if score >= 70 {
fmt.Println("Grade: C")
} else {
fmt.Println("Grade: D")
}
The score variable is scoped to the if/else block — it doesn't leak outside.
For Loop
Go has only for (no while, until, or do-while). For handles all looping needs.
Standard Three-Component For
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 0 1 2 3 4
While-Style For
count := 0
for count < 5 {
fmt.Println(count)
count++
}
// 0 1 2 3 4
Infinite Loop
count := 0
for {
fmt.Println(count)
count++
if count >= 5 {
break
}
}
Range Loop
// Slice/Array
fruits := []string{"apple", "banana", "cherry"}
for index, fruit := range fruits {
fmt.Printf("%d: %s\n", index, fruit)
}
// 0: apple
// 1: banana
// 2: cherry
// Map
ages := map[string]int{"Alice": 25, "Bob": 30}
for name, age := range ages {
fmt.Printf("%s is %d\n", name, age)
}
// String (iterates runes)
for i, r := range "Hello" {
fmt.Printf("%d: %c\n", i, r)
}
// Channel
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
ch <- i
}
close(ch)
}()
for n := range ch {
fmt.Println(n)
}
Break and Continue
for i := 0; i < 10; i++ {
if i == 3 {
continue // Skip 3
}
if i == 8 {
break // Stop at 8
}
fmt.Println(i)
}
// 0 1 2 4 5 6 7
Switch Statement
Go's switch is more flexible than many languages:
Basic Switch
day := "Monday"
switch day {
case "Monday", "Tuesday":
fmt.Println("Weekday start")
case "Friday":
fmt.Println("Almost weekend")
case "Saturday", "Sunday":
fmt.Println("Weekend!")
default:
fmt.Println("Midweek")
}
No break needed — Go automatically breaks after each case.
Expressionless Switch
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("D")
}
Switch with Initialization
switch result := calculate(); {
case result > 100:
fmt.Println("High")
case result > 50:
fmt.Println("Medium")
default:
fmt.Println("Low")
}
Type Switch
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Integer: %d\n", v)
case string:
fmt.Printf("String: %s\n", v)
case bool:
fmt.Printf("Boolean: %v\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
Defer
defer schedules a function call to run after the surrounding function returns:
func main() {
defer fmt.Println("World")
fmt.Print("Hello ")
}
// Hello World
Deferred Cleanup
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // Runs when function returns
// Process file...
return nil
}
Multiple Defers (LIFO Order)
func main() {
defer fmt.Println("First deferred")
defer fmt.Println("Second deferred")
defer fmt.Println("Third deferred")
fmt.Println("Main body")
}
// Main body
// Third deferred
// Second deferred
// First deferred
Common Mistakes
1. Curly Brace on Wrong Line
// Wrong — syntax error
if x > 0
{
fmt.Println("Positive")
}
// Right
if x > 0 {
fmt.Println("Positive")
}
2. Using Break in Switch When You Want Fallthrough
switch x {
case 1:
fmt.Println("One")
// Implicit break — doesn't fall through
case 2:
fmt.Println("Two")
}
// Add explicit 'fallthrough' to continue
3. Forgetting That Range Copies Values
for _, v := range slice {
v = 0 // Modifies copy, not original
}
// Use index: for i := range slice { slice[i] = 0 }
4. Using := with if Initialization and Then Confusing Scope
if x := risky(); x > 0 {
fmt.Println(x) // This x is from the if
}
// fmt.Println(x) // Error: x is undefined here
5. Defer Inside a Loop
for _, file := range files {
f, _ := os.Open(file)
defer f.Close() // Deferred defers don't run until function returns!
}
// Move defer inside a helper function or close explicitly
6. Modifying Loop Variables in Goroutines
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // All goroutines see same i!
}()
}
// Fix: pass i as argument: go func(n int) { fmt.Println(n) }(i)
Practice Questions
1. How do you write a while loop in Go?
Use for with just the condition: for count < 10 { ... }. Go has no while keyword — for handles all looping patterns.
2. What does defer do?
defer schedules a function call to execute immediately before the surrounding function returns. It's typically used for cleanup (closing files, releasing locks).
3. How does Go's switch differ from other languages?
Go's switch doesn't require break statements — it automatically exits after each case. Cases can have multiple values. Switch can be expressionless for if-else chains. Type switches work on interface types.
4. What is the range keyword used for?
range iterates over slices, arrays, maps, strings, and channels. It returns index/element pairs for slices, key/value pairs for maps, and rune/byte pairs for strings.
Challenge: Write a Go program that uses for, switch, defer, and if-with-initialization to Process command-line arguments as a simple calculator.
Solution
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
if len(os.Args) < 4 {
fmt.Println("Usage: calc <a> <op> <b>")
return
}
a, _ := strconv.ParseFloat(os.Args[1], 64)
op := os.Args[2]
b, _ := strconv.ParseFloat(os.Args[3], 64)
var result float64
switch op {
case "+":
result = a + b
case "-":
result = a - b
case "*":
result = a * b
case "/":
if b == 0 {
fmt.Println("Cannot divide by zero")
return
}
result = a / b
default:
fmt.Println("Unknown operator:", op)
return
}
defer fmt.Println("Calculation complete")
fmt.Printf("%.2f %s %.2f = %.2f\n", a, op, b, result)
}
FAQ
{{< faq question="Is there a while loop in Go?" >}}
No. Go has only for. Use for condition { } for while-style loops and for { } for infinite loops. Go's designers felt one loop keyword with multiple forms was simpler.
{{< /faq >}}
{{< faq question="Does go switch need break statements?" >}
No. Go automatically exits a case after execution. Use fallthrough if you want to continue to the next case (rarely needed).
{{< /faq >}}
{{< faq question="What happens if I defer multiple functions?" >} They execute in LIFO (last-in, first-out) order. The last deferred call runs first when the function returns. {{< /faq >}}
{{< faq question="Can I use break in a switch case?" >}
It's not needed — Go breaks automatically. break in a switch is only useful when you need to break out of an enclosing for loop from within a switch.
{{< /faq >}}
{{< faq question="What is the difference between range and a regular for loop?" >}
range is syntactic sugar for iterating over collections. Internally, the compiler generates for loop code. Use range for simplicity and readability.
{{< /faq >}}
Try It Yourself
package main
import (
"fmt"
)
func main() {
// If with init
if n := 10; n%2 == 0 {
fmt.Println(n, "is even")
}
// For as while
sum := 0
for sum < 10 {
sum += 3
fmt.Println("Sum:", sum)
}
// Switch
lang := "Go"
switch lang {
case "Go", "Rust":
fmt.Println("Systems language")
case "Python", "Ruby":
fmt.Println("Scripting language")
default:
fmt.Println("Unknown")
}
// Defer
defer fmt.Println("Cleanup done")
fmt.Println("Processing...")
}
Expected output:
10 is even
Sum: 3
Sum: 6
Sum: 9
Sum: 12
Systems language
Processing...
Cleanup done
What's Next
Now that you understand control flow, learn about functions in Go.
| Topic | Description | Link |
|---|---|---|
| Go Functions | func, returns, multiple returns | {{< ref "06-functions" >}} |
| Go Arrays & Slices | Fixed arrays, slice, append, make | {{< ref "07-arrays-slices" >}} |
| Rust Control Flow | Compare Rust control flow | Rust |