Go: Go Control Flow
Control flow makes programs "think"—Go covers all control flow scenarios with just four simple keywords, eliminating the redundancy of
whileanddo-whileloops.
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
- if conditional statement (including initialization statements)
- switch فرع (automatic break; no manual action required)
- The 4 forms of the for حلقة (alternatives to while and do-while)
- Defer: Deferred Execution and LIFO Order
- Precise jumps with break, continue, and label
- Building an Online Exam Grading System Using Control Flow
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 languageifstatement, 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:
// 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:
// 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:
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 |
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
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:
Passing Grade
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
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
}
Output:
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:
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:
Conversion successful: 123
▶ Example: Reading a file + closing with defer
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")
}
(5) Common practices for error handling in if statements (a Go hallmark)
Error handling in Go is done using if statements. Conventional usage:
if err := doSomething(); err != nil {
return err // early return
}
// Normal Logic
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:
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:
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:
switch n {
case 1:
fmt.Println("One")
fallthrough // force execution of the next case
case 2:
fmt.Println("Two")
}
Output:
One
Two
(3) Switch expression form (without a tag)
Do not follow switch with a variable; write the condition for each case separately:
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:
A
C
▶ Example: Switch with multiple-value matching + HTTP status code routing
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
}
Output:
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
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
fmt.Printf("The %dth time\n", i)
}
}
Output:
The 1th time
The 2th time
The 3th time
▶ Example: "while" style (conditional loop)
package main
import "fmt"
func main() {
n := 1
for n < 5 { // equivalent to while (n < 5)
fmt.Printf("n=%d\n", n)
n++
}
}
Output:
n=1
n=2
n=3
n=4
(3) Infinite loop
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:
Print once per second...
▶ Example: Iterating over a slice, map, or string using range
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)
}
}
Output (map order is random):
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)
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 |
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
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:
1: Start of function
2: Function Body
3: defer execution (before the function returns)
(2) Defer LIFO (last in, first out) order
package main
import "fmt"
func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("Main logic complete")
}
Output:
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
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)
}
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
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:
1 2 3 4 End of loop
▶ Example: continue to skip + increment odd numbers
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))
}
Output:
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:
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:
(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:
// 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):
========================================
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
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
while keyword?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.switch doesn't fall through by default?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.for range?keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys).defer executed? What is the order of execution for multiple defer statements?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.if initialization statement?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."labels used often in real-world projects?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.switch statement support type checking?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{}.continue and break be used in a switch statement?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
- Go covers all flow control structures with just four keywords:
if/switch/for/defer ifsupports initialization statements, which limit the scope of variables to theif-elseblockswitchautomatically breaks by default; usefallthroughexplicitly when you need to proceed through the statement- Four forms of
forthat can replacewhileanddo-while:for init;cond;post{}/for cond{}/for{}/for range deferis executed before the function returns; multipledeferstatements are executed in LIFO (last-in, first-out) order.breakexits the loop,continueskips the current iteration, andbreak labelexits the loop at a specified label- Go's Philosophy of Flow Control Design: Fewer Keywords + Automatic
break+ LIFO Resource Management
📝 Exercises
-
Basic Problem (Difficulty ⭐): Write a FizzBuzz program using
forandswitch: 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". -
Advanced Problem (Difficulty ⭐⭐): Implement the function
countPrimes(n int) int, which returns the number of prime numbers between 1 and n. Requirements: (1) Use aforloop; (2) Use anifstatement to check for prime numbers; (3) Usedeferto print the execution time (usingtime.Now()) before the function returns. -
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 aswitchstatement to determine grades; (3) Usedeferto print the summary information before the function returns.