Control Flow

Control Flow

Imagine you're a chef cooking: if the pan is hot, add oil (if); choose different cooking methods based on the ingredient (switch); keep stirring until it's done (for); don't forget to turn off the heat before plating (defer). Programs work the same way — control flow determines the execution path of code, teaching computers how to "make decisions" and "repeat tasks".


1. Core Concepts

Go's control flow statements are concise yet powerful, mainly including the following categories:

1.1 if / else Conditional

Go's if statement has a unique feature: you can add an initialization statement before the condition, separated by a semicolon ;. The variable's scope is limited to the if/else block.

GO
// Basic form
if condition {
    // Execute when condition is true
} else if otherCondition {
    // Execute when other condition is true
} else {
    // Execute when none of the above match
}

// With initialization statement
if initStatement; condition {
    // Initialized variable is only visible within the if/else block
}

1.2 switch Statement

Go's switch has two key differences from other languages:

GO
switch variable {
case value1:
    // Handle value1
case value2, value3: // Multiple values separated by commas
    // Handle value2 or value3
default:
    // Default handling
}

1.3 for Loop

for is Go's only loop construct. It replaces while, do-while, and other loops found in other languages.

Form Syntax Use Case
Traditional for for init; cond; post {} Known iteration count
While-style for cond {} Conditional loop
Infinite loop for {} Continuous execution
Range iteration for i, v := range collection {} Iterating collections

1.4 break and continue

1.5 defer (Delayed Execution)

The defer statement postpones a function call until the surrounding function returns. Multiple defer calls execute in last-in, first-out (LIFO) stack order.

GO
func example() {
    defer fmt.Println("Registered first, executed last")
    defer fmt.Println("Registered second, executed second-to-last")
    fmt.Println("Normal execution")
}
// Output order: Normal execution → Registered second → Registered first

2. Basic Syntax/Usage

if / else Usage

GO
// Standard if/else
score := 85
if score >= 90 {
    fmt.Println("Excellent")
} else if score >= 80 {
    fmt.Println("Good")
} else if score >= 60 {
    fmt.Println("Pass")
} else {
    fmt.Println("Fail")
}
💡 Tip: In Go, if conditions do not need parentheses, but curly braces {} are required, and else must be on the same line as the closing brace of if. This is a Go syntax rule — not following it will cause a compilation error.

GO
// if with initialization statement
// err's scope is limited to the if/else block; it cannot be accessed outside
if err := doSomething(); err != nil {
    fmt.Println("Error:", err)
}
💡 Tip: if with initialization statements is very common in Go, especially for error handling. It minimizes variable scope and avoids polluting the outer scope.

switch Usage

GO
// Basic switch
day := "Wednesday"
switch day {
case "Monday":
    fmt.Println("New week begins")
case "Tuesday", "Wednesday", "Thursday":
    fmt.Println("Weekday")
case "Friday":
    fmt.Println("Almost weekend")
case "Saturday", "Sunday":
    fmt.Println("Happy weekend")
default:
    fmt.Println("Invalid day")
}
💡 Tip: Go's switch doesn't require break after each case. If you really need to "fall through" to the next case, use the fallthrough keyword, but this is rare.

GO
// Tagless switch (replaces long if-else chains)
score := 85
switch {
case score >= 90:
    fmt.Println("Excellent")
case score >= 80:
    fmt.Println("Good")
case score >= 60:
    fmt.Println("Pass")
default:
    fmt.Println("Fail")
}
💡 Tip: When switch is not followed by a variable, it's equivalent to switch true and can replace lengthy if-else chains for cleaner code.

for Loop Usage

GO
// Traditional for loop
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// While-style
count := 0
for count < 5 {
    fmt.Println(count)
    count++
}

// Infinite loop (use with break)
for {
    fmt.Println("Press Ctrl+C to exit")
    break // Using break here to avoid a true infinite loop
}

defer Usage

GO
func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        fmt.Println("Failed to open file:", err)
        return
    }
    defer file.Close() // Ensure file is closed when function ends
    // ... read file contents ...
}
💡 Tip: defer arguments are evaluated immediately when the defer statement appears, not when the function executes. Be aware of this detail to avoid unexpected behavior.


Example 1: Basic Usage (Difficulty ⭐)

This example demonstrates basic usage of if/else and switch.

GO
package main

import "fmt"

func main() {
    // ===== if/else example: determine grade =====
    score := 78

    // Using if with initialization statement
    // rank variable is only valid within the if/else block
    if rank := score / 10; rank >= 9 {
        fmt.Printf("Score %d, Grade: Excellent\n", score)
    } else if rank >= 8 {
        fmt.Printf("Score %d, Grade: Good\n", score)
    } else if rank >= 6 {
        fmt.Printf("Score %d, Grade: Pass\n", score)
    } else {
        fmt.Printf("Score %d, Grade: Fail\n", score)
    }

    // ===== switch example: determine season by month =====
    month := 8

    switch month {
    case 3, 4, 5:
        fmt.Printf("Month %d is Spring\n", month)
    case 6, 7, 8:
        fmt.Printf("Month %d is Summer\n", month)
    case 9, 10, 11:
        fmt.Printf("Month %d is Autumn\n", month)
    case 12, 1, 2:
        fmt.Printf("Month %d is Winter\n", month)
    default:
        fmt.Printf("%d is not a valid month\n", month)
    }

    // ===== Tagless switch: replaces if-else chain =====
    hour := 14
    switch {
    case hour < 6:
        fmt.Println("Dawn")
    case hour < 12:
        fmt.Println("Morning")
    case hour < 18:
        fmt.Println("Afternoon")
    default:
        fmt.Println("Evening")
    }
}
▶ Try it Yourself

Output:

TEXT
Score 78, Grade: Pass
Month 8 is Summer
Afternoon

Key Points:


Example 2: Intermediate Usage (Difficulty ⭐⭐)

This example demonstrates various forms of for loops and usage of break / continue.

GO
package main

import "fmt"

func main() {
    // ===== Traditional for loop: sum of 1 to 100 =====
    sum := 0
    for i := 1; i <= 100; i++ {
        sum += i
    }
    fmt.Printf("Sum of 1 to 100: %d\n", sum)

    // ===== range over slice =====
    fruits := []string{"Apple", "Banana", "Orange", "Grape"}
    for index, fruit := range fruits {
        fmt.Printf("Index %d: %s\n", index, fruit)
    }

    // ===== range over string (by rune) =====
    text := "GoLang"
    for i, ch := range text {
        fmt.Printf("Position %d: %c (Unicode: %U)\n", i, ch, ch)
    }

    // ===== break: find the first number divisible by 7 =====
    for i := 1; i <= 100; i++ {
        if i%7 == 0 {
            fmt.Printf("First number divisible by 7: %d\n", i)
            break // Exit loop immediately after finding it
        }
    }

    // ===== continue: print all odd numbers from 1-20 =====
    fmt.Print("Odd numbers 1-20: ")
    for i := 1; i <= 20; i++ {
        if i%2 == 0 {
            continue // Skip even numbers, go to next iteration
        }
        fmt.Printf("%d ", i)
    }
    fmt.Println()

    // ===== Labeled break: break out of outer loop =====
    fmt.Println("Searching for number 5 in a 2D array:")
    matrix := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

found: // Label name
    for row, cols := range matrix {
        for col, val := range cols {
            if val == 5 {
                fmt.Printf("Found! Position: row %d, col %d\n", row, col)
                break found // Break out of outer loop
            }
        }
    }
}
▶ Try it Yourself

Output:

TEXT
Sum of 1 to 100: 5050
Index 0: Apple
Index 1: Banana
Index 2: Orange
Index 3: Grape
Position 0: G (Unicode: U+0047)
Position 1: o (Unicode: U+006F)
Position 2: L (Unicode: U+004C)
Position 3: a (Unicode: U+0061)
Position 4: n (Unicode: U+006E)
Position 5: g (Unicode: U+0067)
First number divisible by 7: 7
Odd numbers 1-20: 1 3 5 7 9 11 13 15 17 19
Searching for number 5 in a 2D array:
Found! Position: row 1, col 1

Key Points:


Example 3: Comprehensive Application (Difficulty ⭐⭐⭐)

This example combines all control flow statements to implement a simple student grade management system.

GO
package main

import "fmt"

// Student struct
type Student struct {
    Name   string
    Scores []int
}

// GetAverage calculates the average score
func (s Student) GetAverage() float64 {
    if len(s.Scores) == 0 {
        return 0
    }
    total := 0
    for _, score := range s.Scores {
        total += score
    }
    return float64(total) / float64(len(s.Scores))
}

// GetGrade returns the grade based on average score
func (s Student) GetGrade() string {
    avg := s.GetAverage()
    switch {
    case avg >= 90:
        return "A (Excellent)"
    case avg >= 80:
        return "B (Good)"
    case avg >= 70:
        return "C (Average)"
    case avg >= 60:
        return "D (Pass)"
    default:
        return "F (Fail)"
    }
}

// PrintReport prints the grade report
func PrintReport(students []Student) {
    fmt.Println("========================================")
    fmt.Println("         Student Grade Report")
    fmt.Println("========================================")

    // Use defer to ensure the report ending prints after all student data
    defer fmt.Println("========================================")
    defer fmt.Println("         Report Complete")

    for i, student := range students {
        // Use defer to demonstrate LIFO order
        defer func(name string, idx int) {
            fmt.Printf("[Cleanup] Finished processing %s's report\n", name)
        }(student.Name, i)

        // Print student info
        fmt.Printf("\nStudent %d: %s\n", i+1, student.Name)
        fmt.Printf("  Scores: %v\n", student.Scores)
        fmt.Printf("  Average: %.1f\n", student.GetAverage())
        fmt.Printf("  Grade: %s\n", student.GetGrade())

        // Analyze each subject score
        subjects := []string{"Chinese", "Math", "English"}
        for j, score := range student.Scores {
            // Ensure index is within bounds
            subject := "Unknown"
            if j < len(subjects) {
                subject = subjects[j]
            }

            // Check each subject score
            if score < 60 {
                fmt.Printf("  ⚠ %s (%d) failed, needs retake\n", subject, score)
            }
        }
    }
}

// FindTopStudent finds the student with the highest average
func FindTopStudent(students []Student) (string, float64) {
    if len(students) == 0 {
        return "", 0
    }

    topName := students[0].Name
    topAvg := students[0].GetAverage()

    for _, s := range students[1:] {
        if avg := s.GetAverage(); avg > topAvg {
            topName = s.Name
            topAvg = avg
        }
    }

    return topName, topAvg
}

func main() {
    // Create student data
    students := []Student{
        {"Alice", []int{85, 92, 78}},
        {"Bob", []int{90, 95, 88}},
        {"Charlie", []int{72, 58, 65}},
        {"Diana", []int{95, 98, 92}},
    }

    // Print grade report
    PrintReport(students)

    // Find top student
    topName, topAvg := FindTopStudent(students)
    fmt.Printf("\n🏆 Top Student: %s (Average: %.1f)\n", topName, topAvg)
}
▶ Try it Yourself

Output:

TEXT
========================================
         Student Grade Report
========================================

Student 1: Alice
  Scores: [85 92 78]
  Average: 85.0
  Grade: B (Good)

Student 2: Bob
  Scores: [90 95 88]
  Average: 91.0
  Grade: A (Excellent)

Student 3: Charlie
  Scores: [72 58 65]
  Average: 65.0
  Grade: D (Pass)
  ⚠ Math (58) failed, needs retake

Student 4: Diana
  Scores: [95 98 92]
  Average: 95.0
  Grade: A (Excellent)
[Cleanup] Finished processing Diana's report
[Cleanup] Finished processing Charlie's report
[Cleanup] Finished processing Bob's report
[Cleanup] Finished processing Alice's report
========================================
         Report Complete
========================================

🏆 Top Student: Diana (Average: 95.0)

Key Points:


3. Common Use Cases

Case 1: Error Handling Chain (if + initialization statement)

In Go, the most common use of if with initialization statements is error handling:

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Parse numbers and handle errors
    inputs := []string{"42", "abc", "100", "xyz", "0"}

    for _, input := range inputs {
        // Perform type conversion in initialization, check for error in condition
        if num, err := strconv.Atoi(input); err != nil {
            fmt.Printf("❌ Cannot parse %q: %v\n", input, err)
        } else {
            fmt.Printf("✅ Parsed successfully: %q → %d\n", input, num)
        }
    }
}

Case 2: Iterate Map with Conditional Filtering (for + switch)

GO
package main

import "fmt"

func main() {
    // Student grade table
    students := map[string]int{
        "Alice":   85,
        "Bob":     92,
        "Charlie": 58,
        "Diana":   76,
        "Eve":     45,
    }

    // Count students in each grade level
    excellent, good, pass, fail := 0, 0, 0, 0

    for name, score := range students {
        switch {
        case score >= 90:
            excellent++
            fmt.Printf("⭐ %s: %d (Excellent)\n", name, score)
        case score >= 80:
            good++
            fmt.Printf("👍 %s: %d (Good)\n", name, score)
        case score >= 60:
            pass++
            fmt.Printf("✅ %s: %d (Pass)\n", name, score)
        default:
            fail++
            fmt.Printf("❌ %s: %d (Fail)\n", name, score)
        }
    }

    fmt.Printf("\nSummary: Excellent %d, Good %d, Pass %d, Fail %d\n",
        excellent, good, pass, fail)
}

❓ FAQ

Q1: Why doesn't Go's switch need break?

Go's designers believed that C's switch fallthrough behavior was a common source of bugs. Go's switch automatically exits after each case, preventing accidental fallthrough from forgotten break statements. If fallthrough is truly needed, you can explicitly use the fallthrough keyword.

GO
// fallthrough usage (rare)
x := 1
switch x {
case 1:
    fmt.Println("one")
    fallthrough // Continue executing the next case's code
case 2:
    fmt.Println("two") // Will be executed
default:
    fmt.Println("other")
}
// Output: one \n two

Q2: Are variables in for range copies?

Yes. The value variable in for range is a copy of the element; modifying it won't affect the original collection. If you need to modify elements in the original collection, access them by index.

GO
nums := []int{1, 2, 3, 4, 5}

// ❌ Wrong: modifying a copy
for _, n := range nums {
    n *= 2 // Won't affect nums
}

// ✅ Correct: modify original slice via index
for i := range nums {
    nums[i] *= 2 // Will modify nums
}

Q3: When are defer arguments evaluated?

defer arguments are evaluated when the defer statement appears, not when the function returns.

GO
func main() {
    x := 10
    defer fmt.Println("x in defer:", x) // x is evaluated here as 10
    x = 20
    fmt.Println("x after modification:", x)
}
// Output:
// x after modification: 20
// x in defer: 10  (not 20!)

Q4: How to correctly capture loop variables when using goroutines in a loop?

Before Go 1.22, for loop variables shared the same address across all iterations, which could cause issues in goroutines. Go 1.22+ fixed this behavior — each iteration gets its own variable. If using an older version, pass the variable as an argument:

GO
// Go 1.22+ — just use directly
for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i) // Each goroutine gets its own i
    }()
}

// Older versions — explicitly pass as argument
for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n) // n is a copy of i
    }(i)
}

📖 Summary


📝 Exercises

Exercise 1 (⭐)

Write a program that takes a year as input and determines whether it's a leap year. Rules:

GO
package main

import "fmt"

func main() {
    year := 2024 // Change this value to test

    // Write your code here
    // Hint: Use if init; cond form
}

Exercise 2 (⭐⭐)

Write a program that uses for loops to print a multiplication table. Format requirements:

GO
package main

import "fmt"

func main() {
    // Write your code here
    // Hint: Use two nested for loops
    // Outer loop i from 1 to 9
    // Inner loop j from 1 to i
}

Exercise 3 (⭐⭐⭐)

Write a program that implements a simple number guessing game:

  1. Use a loop to let the user guess repeatedly until correct
  2. After each guess, give a hint: "too high" or "too low"
  3. Use defer to record the game's start and end times
  4. Use switch to give different ratings based on the number of guesses (1-3: genius, 4-6: good, 7+: keep trying)
GO
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // Use defer to record end time
    start := time.Now()
    defer func() {
        fmt.Printf("\nGame duration: %v\n", time.Since(start))
    }()

    // Generate random number 1-100
    target := rand.Intn(100) + 1
    attempts := 0

    fmt.Println("=== Number Guessing Game ===")
    fmt.Println("I'm thinking of a number between 1-100, try to guess it!")

    // Write your code here
    // Hints:
    // 1. Use a for loop to keep the game going
    // 2. Use fmt.Scan to read user input
    // 3. Use switch to check guess result and rating level
    // 4. Use break to exit the loop when guessed correctly
}

Next Lesson

Next Lesson: Functions → Learn Go's function definitions, multiple return values, variadic parameters, anonymous functions, closures, and other core concepts.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏