Go: Go Control Flow

Control flow makes programs "think"—Go covers all control flow scenarios with just four simple keywords, eliminating the redundancy of while and do-while loops.

Go's control flow design philosophy: Less is more. The four keywords if, switch, for, and defer, combined with break, continue, and label, are sufficient to handle 99% of business logic. In this lesson, you'll master all the core concepts of Go's flow control.

1. You will learn



2. A True Story About an Online Education Platform

(1) Pain Point: 500 lines of C++ code for grading exams, yet bugs keep cropping up

Charlie is a واجهة خلفية engineer at an online education platform. He has just taken over the code for an automatic grading system:

"The PM said that it now takes 8 hours to process 1,000 exam papers, and every time a bug pops up, it's either an 'out of range' خطأ or a missing {} in a C language if statement, which causes the grades for the entire grade to be messed up..."

He opened the C++ code left behind by his predecessor and took a look:

CPP
// C++ Style: Verbose + Prone to Errors
if (score >= 60) {
    grade = "Passing Grade";
}
else  // Note: C++ else on a different line is valid, but team style is inconsistent
    grade = "Fail";

// Omission of a loop condition = an infinite loop
while (true) {  // Wanted 3 iterations, but forgot the counter
    // ... Didn't realize until 500 lines later
}

After taking over, Charlie decided to rewrite it in Go.

(2) Solution in Go

Go covers the entire process with four keywords:

GO
// score_evaluator.go
package main

import "fmt"

func evaluateScore(score int) string {
    // (1) if contains an initialization statement
    if grade := calculateGrade(score); grade != "" {
        return fmt.Sprintf("Score: %d, Level: %s", score, grade)
    }
    return "Invalid Score"
}

func calculateGrade(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    // (2) for 4 forms
    scores := []int{95, 82, 67, 45, 88}

    for i, s := range scores {  // range form
        fmt.Printf("Question %d: %d points -> %s\n", i+1, s, evaluateScore(s))
    }
}

Output:

TEXT 📖 Display only
Question 1: 95 points -> Score: 95, Level A
Question 2: 82 points -> Score: 82, Level B
Question 3: 67 points -> Score: 67, Level C
Question 4: 45 points -> Score: 45, Level D
Question 5: 88 points -> Score: 88, Level B

(3) Benefits: Comparison of Process Control Simplicity

Language if keyword loop keyword switch automatic break Code volume for scoring system
C/C++ if for/while/do-while Requires manual break ~500 lines
Java if for/while/do-while Requires manual break ~400 lines
Python if for/while N/A (no switch) ~350 lines
Go if for (4 forms) automatic break ~200 lines
💡 Tip: Go unifies all loops into for and removes break from the default behavior of switch—one less keyword, ten fewer types of bugs.



3. if statement

(1) Basic if / else

GO
package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("Excellent")
    } else if score >= 60 {
        fmt.Println("Passing Grade")
    } else {
        fmt.Println("Fail")
    }
}

Output:

TEXT 📖 Display only
Passing Grade
🔥 Common Mistake: In Go, the else keyword must be on the same line as } (curly brace style is required). The }else{ syntax results in a compilation error.

▶ Example: Grade Determination

GO
package main

import "fmt"

func judgeGrade(score int) string {
    if score >= 90 {
        return "A"
    } else if score >= 80 {
        return "B"
    } else if score >= 70 {
        return "C"
    } else if score >= 60 {
        return "D"
    } else {
        return "F"
    }
}

func main() {
    fmt.Println(judgeGrade(95))  // A
    fmt.Println(judgeGrade(72))  // C
    fmt.Println(judgeGrade(50))  // F
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
A
C
F

(3) if initialization statements (a Go feature)

if can execute an initialization statement first, followed by a conditional check—limiting the scope of variables:

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Syntax: if initialization statement; condition { ... }
    // The variable `err` is scoped only within the `if-else` block.
    if n, err := strconv.Atoi("123"); err == nil {
        fmt.Printf("Conversion successful: %d\n", n)
    } else {
        fmt.Printf("Conversion failed: %v\n", err)
    }
}

Output:

TEXT 📖 Display only
Conversion successful: 123

▶ Example: Reading a file + closing with defer

GO
package main

import (
    "fmt"
    "os"
)

func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // automatically close file before function returns

    data := make([]byte, 1024)
    n, _ := file.Read(data)
    fmt.Printf("%d bytes read\n", n)

    return nil
}

func main() {
    readFile("config.txt")
}
▶ Try it Yourself

(5) Common practices for error handling in if statements (a Go hallmark)

Error handling in Go is done using if statements. Conventional usage:

GO
if err := doSomething(); err != nil {
    return err  // early return
}
// Normal Logic
💡 Tip: Lesson 8 will provide an in-depth explanation of the error type and best practices for this pattern.



4. switch branch (automatic break)

(1) Switch Basics: Automatic Break

By default, Go's switch statement does not propagate—it automatically exits after matching a case:

GO
package main

import "fmt"

func main() {
    day := "Monday"

    switch day {
    case "Monday":
        fmt.Println("Monday: Meeting Day")
    case "Tuesday":
        fmt.Println("Tuesday: Development Day")
    case "Friday":
        fmt.Println("Friday: Demo Day")
    default:
        fmt.Println("Weekend: Day off")
    }
}

Output:

TEXT 📖 Display only
Monday: Meeting Day

(2) vs. C/Java: No need for a break

Language Syntax Automatic break
C/C++ case 1: ...; case 2: ... Requires a manual break;
Java case 1: ...; case 2: ... Requires a manual break;
Go case "Monday": ... Automatic break

If you want to use fall-through, use the fallthrough keyword:

GO
switch n {
case 1:
    fmt.Println("One")
    fallthrough  // force execution of the next case
case 2:
    fmt.Println("Two")
}

Output:

TEXT 📖 Display only
One
Two

(3) Switch expression form (without a tag)

Do not follow switch with a variable; write the condition for each case separately:

GO
package main

import "fmt"

func gradeLevel(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    fmt.Println(gradeLevel(95))
    fmt.Println(gradeLevel(72))
}

Output:

TEXT 📖 Display only
A
C

▶ Example: Switch with multiple-value matching + HTTP status code routing

GO
package main

import "fmt"

func routeStatus(code int) string {
    switch code {
    case 200, 201, 204:
        return "Success"
    case 301, 302:
        return "Redirect"
    case 400, 401, 403, 404:
        return "Client Error"
    case 500, 502, 503:
        return "Server Error"
    default:
        return "Unknown"
    }
}

func main() {
    fmt.Println(routeStatus(200))  // Success
    fmt.Println(routeStatus(404))  // Client Error
    fmt.Println(routeStatus(500))  // Server Error
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Success
Client Error
Server Error


5. The 4 Forms of the for Loop

Go unifies all loops under the for construct, using different syntax to implement while, do-while, and infinite loops.

(1) The classic three-part "for" loop

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 3; i++ {
        fmt.Printf("The %dth time\n", i)
    }
}

Output:

TEXT 📖 Display only
The 1th time
The 2th time
The 3th time

▶ Example: "while" style (conditional loop)

GO
package main

import "fmt"

func main() {
    n := 1
    for n < 5 {  // equivalent to while (n < 5)
        fmt.Printf("n=%d\n", n)
        n++
    }
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
n=1
n=2
n=3
n=4

(3) Infinite loop

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // Classic Infinite Loop (In real-world scenarios, it would use `break` or `return`)
    for {
        fmt.Println("Print once per second...")
        time.Sleep(1 * time.Second)
        break  // prevent actual infinite loop, for demo only
    }
}

Output:

TEXT 📖 Display only
Print once per second...

▶ Example: Iterating over a slice, map, or string using range

GO
package main

import "fmt"

func main() {
    scores := []int{95, 82, 67}

    // Iterate through the slice: index, value
    for i, score := range scores {
        fmt.Printf("Problem %d: %d points\n", i+1, score)
    }

    // If only value is present: use _ to ignore index
    for _, score := range scores {
        fmt.Printf("Score: %d\n", score)
    }

    // Iterate through a map (key-value)
    ages := map[string]int{"Alice": 28, "Bob": 32}
    for name, age := range ages {
        fmt.Printf("%s is %d years old\n", name, age)
    }

    // Iterate over a string (by rune, not by byte)
    for i, r := range "Go" {
        fmt.Printf("Position %d: %c (code point %d)\n", i, r, r)
    }
}
▶ Try it Yourself

Output (map order is random):

TEXT 📖 Display only
Problem 1: 95 points
Problem 2: 82 points
Problem 3: 67 points
Score: 95
Score: 82
Score: 67
Alice is 28 years old
Bob is 32 years old
Position 0: G (code point 71)
Position 1: o (code point 111)
🔥 Common Mistake: The order of range map is not guaranteed—it may vary with each run. If you need a consistent order, sort the keys first before iterating.

(5) Quick Reference for the Four Forms of "for"

Form Syntax Alternative
Standard for for i := 0; i < n; i++ {} C/Java for
"while" style for condition {} C/Java while
Infinite Loop for {} while(true)
range iteration for i, v := range slice {} Python for...in
100%
flowchart LR
    A[for init; condition; post] --> B{condition true?}
    B -->|yes| C[execute loop body]
    C --> D[execute post statement]
    D --> B
    B -->|no| E[exit loop]
    style A stroke-width:2px
    style E stroke:#f44,stroke-width:2px


6. defer: Deferred execution (LIFO order)

defer defers the execution of a statement until just before the current function returns. It is commonly used for resource cleanup (closing files, unlocking resources, and closing connections).

(1) defer Basics

GO
package main

import "fmt"

func main() {
    fmt.Println("1: Start of function")

    defer fmt.Println("3: defer execution (before the function returns)")

    fmt.Println("2: Function Body")
}

Output:

TEXT 📖 Display only
1: Start of function
2: Function Body
3: defer execution (before the function returns)

(2) Defer LIFO (last in, first out) order

GO
package main

import "fmt"

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")

    fmt.Println("Main logic complete")
}

Output:

TEXT 📖 Display only
Main logic complete
3
2
1

Why LIFO? Because defer simulates "stack-based resource management"—resources opened last are closed first.

▶ Example: Practical Use of defer — Closing a Database Connection

GO
package main

import (
    "database/sql"
    "fmt"
    "log"
)

func queryUser(db *sql.DB, userID int) {
    // Get the connection
    rows, err := db.Query("SELECT name FROM users WHERE id = ?", userID)
    if err != nil {
        log.Fatal(err)
    }

    // Key Point: `defer` releases resources (LIFO)
    defer rows.Close()  // Close result set when function returns

    // Business logic...
    for rows.Next() {
        var name string
        rows.Scan(&name)
        fmt.Printf("User: %s\n", name)
    }
}

func main() {
    fmt.Println("Example of Resource Management with `defer`")
    // Real-world scenario: db, _ := sql.Open("mysql", "...")
    // queryUser(db, 1)
}
▶ Try it Yourself
💡 Tip: defer file.Close() will execute even if return is called in the middle—this is a signature use case of Go's resource management. Note: only defer the cleanup of resources you opened in this function—do not defer db.Close() here since db was passed in and its lifecycle is managed by the caller.



7. break / continue / label: Precise Jumps

(1) break: Exit the loop

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 10; i++ {
        if i == 5 {
            break  // exit the entire loop
        }
        fmt.Printf("%d ", i)
    }
    fmt.Println("End of loop")
}

Output:

TEXT 📖 Display only
1 2 3 4 End of loop

▶ Example: continue to skip + increment odd numbers

GO
package main

import "fmt"

func sumOdd(n int) int {
    sum := 0
    for i := 1; i <= n; i++ {
        if i%2 == 0 {
            continue  // skip even numbers
        }
        sum += i
    }
    return sum
}

func main() {
    fmt.Printf("1-10: Sum of odd numbers: %d\n", sumOdd(10))
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
1-10: Sum of odd numbers: 25

(3) Use a label to break out of a nested loop

label names the loop; break label exits the specified loop:

GO
package main

import "fmt"

func main() {
outer:
    for i := 1; i <= 3; i++ {
        for j := 1; j <= 3; j++ {
            if i*j == 6 {
                fmt.Printf("Hit: i=%d, j=%d\n", i, j)
                break outer  // directly exit the outer loop
            }
            fmt.Printf("(%d, %d) ", i, j)
        }
    }
    fmt.Println("Done")
}

Output:

TEXT 📖 Display only
(1, 1) (1, 2) (1, 3) (2, 1) (2, 2) (2, 3) (3, 1) (3, 2) Hit: i=3, j=2
Done

(4) Comparison of the Three Types of Redirects

Keyword Function Out of Scope
break Exits the current loop or switch statement Single-level
continue Skip the remainder of this iteration Single-level
break label Exit the loop at the specified label Nested


8. Complete Example: Online Exam Grading System

Link all the control flows in this lesson together to implement a multidimensional exam grader:

GO
// exam_evaluator.go
package main

import "fmt"

// Structure of the Problem
type Question struct {
    Type    string  // single/multi/judge
    Correct string  // correct answer
    Student string  // student answer
    Score   float64 // score value
}

// Scoring Function: Initialized with an `if` statement + `switch` expression + `for range`
func evaluateExam(name string, questions []Question) (total float64, details []string) {
    defer func() {
        // defer: Collect results (ensures that results are collected even if a return statement is encountered midway)
        details = append(details, fmt.Sprintf("[%s] Total score: %.1f", name, total))
    }()

    for i, q := range questions {
        var earned float64

        // switch expression format: multi-condition branching
        switch q.Type {
        case "single":
            if q.Student == q.Correct {
                earned = q.Score
            }
        case "judge":
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        case "multi":
            // Multiple-choice question: Points are awarded only for exact matches
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        default:
            continue  // skip unknown question type
        }

        total += earned
        details = append(details,
            fmt.Sprintf("Problem %d (%s): Scored %.1f points", i+1, q.Type, earned))
    }

    return total, details
}

func main() {
    questions := []Question{
        {Type: "single", Correct: "B", Student: "B", Score: 2},
        {Type: "judge", Correct: "true", Student: "true", Score: 1},
        {Type: "multi", Correct: "A,B,C", Student: "A,B", Score: 3},
        {Type: "single", Correct: "D", Student: "D", Score: 2},
    }

    // Use a `for range` loop to iterate through the student list
    students := []string{"Alice", "Bob", "Charlie"}
    for _, name := range students {
        total, details := evaluateExam(name, questions)

        fmt.Println("========================================")
        for _, d := range details {
            fmt.Println(d)
        }
    }
}

Expected Output (Detailed Grades for Each Student + Defer Summary):

TEXT 📖 Display only
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Alice] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Bob] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Charlie] Total score: 5.0
🔥 Common mistake: You must modify the named return value in defer for it to be visible to the outer scope—in the example above, details uses the named return value (total float64, details []string), which is why the append inside defer takes effect.


❓ FAQ

Q Why doesn't Go have a while keyword?
A Go uses for for all loops: for condition {} is equivalent to while, and for {} is equivalent to while(true). With one fewer keyword, teams don't have to argue over whether to use while or do-while.
Q Isn't it a hassle that switch doesn't fall through by default?
A On the contrary, the Go team believes that 90% of switch cases should be self-contained—the "forgetting to write break, causing fall-through" issue in C and Java is one of the classic bugs. Go uses fallthrough to explicitly indicate the intent to fall through, which is safer.
Q Why is the order of the map random when iterating with for range?
A Go is intentionally designed to be unordered—to prevent programmers from relying on the order of the map. If you need a consistent order, sort the keys before iterating: keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys).
Q When is defer executed? What is the order of execution for multiple defer statements?
A defer is executed before the current function returns (i.e., after the return statement is executed but before the function actually exits). Multiple defer statements are executed in LIFO (last-in, first-out) order, simulating stack-based resource management: resources pushed onto the stack last are released first.
Q What is the scope of an if initialization statement?
A Variables declared in an if initialization statement are only visible within the if-else block; they become invalid once the if-else block is exited. This prevents the common bug of "variables leaking into the outer scope."
Q Are labels used often in real-world projects?
A Rarely. label is mainly used to break out of nested loops; in 99% of cases, break combined with return is sufficient. Overusing label reduces code readability—a lesson learned from the goto era.
Q Does Go's switch statement support type checking?
A Yes. switch x := y.(type) is used for interface type checking; we'll cover this in more detail in Lesson 7. However, this syntax only applies to variables of type interface{}.
Q Can continue and break be used in a switch statement?
A Yes, but they behave differently. break exits the switch statement; in a for loop, it exits the for loop. continue can only be used in a for loop; it cannot be used in a switch statement (since a switch is not considered a loop).

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Write a FizzBuzz program using for and switch: Print the numbers from 1 to 100. When you encounter a multiple of 3, print "Fizz"; when you encounter a multiple of 5, print "Buzz"; and when you encounter a multiple of 15, print "FizzBuzz".

  2. Advanced Problem (Difficulty ⭐⭐): Implement the function countPrimes(n int) int, which returns the number of prime numbers between 1 and n. Requirements: (1) Use a for loop; (2) Use an if statement to check for prime numbers; (3) Use defer to print the execution time (using time.Now()) before the function returns.

  3. Challenge Problem (Difficulty ⭐⭐⭐): Implement a student grade analyzer: Given a set of scores ([]float64), output (1) the average score; (2) the pass rate (≥60 points); (3) the highest and lowest scores; (4) a breakdown by grade (A/B/C/D/F). Requirements: (1) Use for range; (2) Use a switch statement to determine grades; (3) Use defer to print the summary information before the function returns.

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%

🙏 帮我们做得更好

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

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