Why This Matters - The Foundation of Program Logic
Control flow determines how your program makes decisions and repeats actions. It's the intelligence behind every application - from simple calculators to complex AI systems. Understanding control flow is fundamental because:
- Decision Making: Programs constantly choose between different paths based on conditions
- Efficiency: The right control structures make code faster and more readable
- Logic Building: Complex behaviors are built from simple control flow patterns
- Problem Solving: Most algorithms are fundamentally about controlling execution flow
Real-World Impact: Every application you use relies on control flow - web servers route requests, games handle user input, data pipelines process information, and APIs validate requests. Mastering control flow means you can build any kind of program.
Learning Objectives
By the end of this article, you will:
- ✅ Understand Go's simplified control flow philosophy
- ✅ Master conditional logic with
ifstatements and initialization - ✅ Use
forloops in all their forms - ✅ Iterate over collections efficiently with
range - ✅ Write clean multi-way branching with
switchstatements - ✅ Use
deferfor reliable resource cleanup - ✅ Apply control flow patterns to solve real-world problems
- ✅ Avoid common pitfalls that cause bugs and performance issues
Go's Control Flow Philosophy - Less is More
Go deliberately simplifies control flow by providing fewer, more consistent constructs:
1// ❌ Other languages have multiple ways to loop:
2while { } // While loop
3do { } while; // Do-while loop
4for { } // Classic for loop
5foreach { } // For-each loop
6
7// ✅ Go: One loop that does everything efficiently
8for condition { } // While-style loop
9for i := 0; i < n; i++ { } // Classic loop
10for _, item := range collection { } // Range iteration
11for { } // Infinite loop
Why Go Chose Simplicity:
- Reduced Cognitive Load: One way to do things = easier to read and write
- Fewer Bugs: Less syntax variations = fewer places to make mistakes
- Consistency: Same patterns everywhere = predictable code
- Performance: Go's
foris highly optimized for all use cases
Key Design Decisions:
- No parentheses around conditions
- No ternary operator
- No
while,do-while - No automatic fallthrough in
switch
💡 Key Insight: Go's constraints are liberating - fewer choices mean you spend less time deciding and more time building.
Core Concepts - Understanding Control Flow Building Blocks
Conditional Logic: Making Decisions
Conditional logic is about choosing between different paths based on conditions. Think of it as your program's decision-making ability.
Real-World Example: A thermostat system
- If temperature > 75°F: turn on air conditioning
- If temperature < 65°F: turn on heating
- Otherwise: maintain current state
1// Simple conditional example
2temperature := 72
3if temperature > 75 {
4 fmt.Println("Turning on AC")
5} else if temperature < 65 {
6 fmt.Println("Turning on heat")
7} else {
8 fmt.Println("Maintaining current temperature")
9}
The Anatomy of Conditional Logic:
Conditional statements evaluate boolean expressions and execute different code paths based on the result. In Go, every condition must evaluate to a boolean value - there's no concept of "truthy" or "falsy" values like in some other languages.
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Boolean expressions in Go
8 age := 25
9 hasLicense := true
10
11 // Simple boolean evaluation
12 if age >= 18 {
13 fmt.Println("Adult")
14 }
15
16 // Compound conditions with logical operators
17 if age >= 18 && hasLicense {
18 fmt.Println("Can drive")
19 }
20
21 // Negation
22 if !hasLicense {
23 fmt.Println("Cannot drive - no license")
24 }
25
26 // Multiple else-if chains
27 if age < 13 {
28 fmt.Println("Child")
29 } else if age < 20 {
30 fmt.Println("Teenager")
31 } else if age < 65 {
32 fmt.Println("Adult")
33 } else {
34 fmt.Println("Senior")
35 }
36}
Understanding Boolean Operators:
Go provides three primary boolean operators for combining conditions:
&&(AND): Both conditions must be true||(OR): At least one condition must be true!(NOT): Inverts the boolean value
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // AND operator - both must be true
8 fmt.Println("AND truth table:")
9 fmt.Printf("true && true = %t\n", true && true)
10 fmt.Printf("true && false = %t\n", true && false)
11 fmt.Printf("false && true = %t\n", false && true)
12 fmt.Printf("false && false = %t\n", false && false)
13
14 // OR operator - at least one must be true
15 fmt.Println("\nOR truth table:")
16 fmt.Printf("true || true = %t\n", true || true)
17 fmt.Printf("true || false = %t\n", true || false)
18 fmt.Printf("false || true = %t\n", false || true)
19 fmt.Printf("false || false = %t\n", false || false)
20
21 // NOT operator - inverts the value
22 fmt.Println("\nNOT operator:")
23 fmt.Printf("!true = %t\n", !true)
24 fmt.Printf("!false = %t\n", !false)
25
26 // Short-circuit evaluation
27 fmt.Println("\nShort-circuit behavior:")
28 x := 5
29 // The second condition is never evaluated if first is false
30 if x > 10 && expensiveCheck() {
31 fmt.Println("Both conditions true")
32 } else {
33 fmt.Println("First condition was false, second never checked")
34 }
35}
36
37func expensiveCheck() bool {
38 fmt.Println(" Expensive check called!")
39 return true
40}
Repetition: Doing Things Multiple Times
Loops allow programs to repeat actions efficiently. Instead of writing the same code 100 times, you write it once and tell Go to repeat it.
Real-World Example: Processing customer orders
- For each customer in line: take their order
- For each item in cart: scan and price it
- For each payment method: validate and process
1// Simple loop example
2customers := []string{"Alice", "Bob", "Charlie"}
3for _, customer := range customers {
4 fmt.Printf("Processing order for %s\n", customer)
5}
Loop Mechanics and Control:
Go's for loop is remarkably versatile because it's designed to handle every looping scenario. Understanding how loops work internally helps you write more efficient code.
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Loop initialization, condition, and post statement
8 fmt.Println("Classic for loop anatomy:")
9 for i := 0; i < 5; i++ {
10 fmt.Printf("Iteration %d\n", i)
11 }
12
13 // The parts of a for loop:
14 // 1. Init statement: i := 0 (runs once before loop starts)
15 // 2. Condition: i < 5 (checked before each iteration)
16 // 3. Post statement: i++ (runs after each iteration)
17
18 // While-style loop (only condition)
19 fmt.Println("\nWhile-style loop:")
20 count := 0
21 for count < 3 {
22 fmt.Printf("Count: %d\n", count)
23 count++
24 }
25
26 // Infinite loop with manual break
27 fmt.Println("\nInfinite loop with break:")
28 n := 0
29 for {
30 if n >= 3 {
31 break // Exit the loop
32 }
33 fmt.Printf("N: %d\n", n)
34 n++
35 }
36
37 // Continue statement - skip to next iteration
38 fmt.Println("\nUsing continue:")
39 for i := 0; i < 5; i++ {
40 if i == 2 {
41 continue // Skip iteration when i is 2
42 }
43 fmt.Printf("Processing %d\n", i)
44 }
45}
Nested Loops and Complexity:
Nested loops are powerful but require careful consideration of their time complexity. Each nested loop multiplies the number of iterations.
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Single loop: O(n) - linear time
8 fmt.Println("Single loop (5 iterations):")
9 iterations := 0
10 for i := 0; i < 5; i++ {
11 iterations++
12 }
13 fmt.Printf("Total iterations: %d\n\n", iterations)
14
15 // Nested loop: O(n²) - quadratic time
16 fmt.Println("Nested loop (5x5 = 25 iterations):")
17 iterations = 0
18 for i := 0; i < 5; i++ {
19 for j := 0; j < 5; j++ {
20 iterations++
21 if j < 3 { // Print first few to show pattern
22 fmt.Printf("[%d,%d] ", i, j)
23 }
24 }
25 if i < 3 {
26 fmt.Println()
27 }
28 }
29 fmt.Printf("\nTotal iterations: %d\n\n", iterations)
30
31 // Triple nested loop: O(n³) - cubic time
32 fmt.Println("Triple nested loop (3x3x3 = 27 iterations):")
33 iterations = 0
34 for i := 0; i < 3; i++ {
35 for j := 0; j < 3; j++ {
36 for k := 0; k < 3; k++ {
37 iterations++
38 }
39 }
40 }
41 fmt.Printf("Total iterations: %d\n", iterations)
42
43 // Practical example: Matrix multiplication
44 fmt.Println("\nMatrix multiplication pattern:")
45 matrixA := [][]int{
46 {1, 2, 3},
47 {4, 5, 6},
48 }
49 matrixB := [][]int{
50 {7, 8},
51 {9, 10},
52 {11, 12},
53 }
54
55 // Result will be 2x2
56 result := make([][]int, len(matrixA))
57 for i := range result {
58 result[i] = make([]int, len(matrixB[0]))
59 }
60
61 // Three nested loops: i for rows, j for cols, k for multiplication
62 for i := 0; i < len(matrixA); i++ {
63 for j := 0; j < len(matrixB[0]); j++ {
64 sum := 0
65 for k := 0; k < len(matrixB); k++ {
66 sum += matrixA[i][k] * matrixB[k][j]
67 }
68 result[i][j] = sum
69 }
70 }
71
72 fmt.Println("Result matrix:")
73 for _, row := range result {
74 fmt.Println(row)
75 }
76}
Selection: Choosing Between Multiple Options
Switch statements provide clean ways to choose between many options, replacing long chains of if-else statements.
Real-World Example: Menu-driven application
- If user presses '1': show account balance
- If user presses '2': show transaction history
- If user presses '3': transfer funds
- If user presses '4': exit
Switch Statement Power:
Go's switch statements are more powerful than in most languages. They don't require break statements, can switch on any comparable type, and can even have no expression at all.
1// run
2package main
3
4import (
5 "fmt"
6 "time"
7)
8
9func main() {
10 // Traditional switch on value
11 day := time.Now().Weekday()
12 fmt.Printf("Today is %v\n", day)
13
14 switch day {
15 case time.Monday:
16 fmt.Println("Start of work week")
17 case time.Friday:
18 fmt.Println("End of work week")
19 case time.Saturday, time.Sunday:
20 fmt.Println("Weekend!")
21 default:
22 fmt.Println("Midweek day")
23 }
24
25 // Switch without expression (replaces if-else chains)
26 hour := time.Now().Hour()
27 fmt.Printf("\nCurrent hour: %d\n", hour)
28
29 switch {
30 case hour < 6:
31 fmt.Println("Early morning")
32 case hour < 12:
33 fmt.Println("Morning")
34 case hour < 17:
35 fmt.Println("Afternoon")
36 case hour < 21:
37 fmt.Println("Evening")
38 default:
39 fmt.Println("Night")
40 }
41
42 // Type switch
43 var value interface{} = 42
44
45 switch v := value.(type) {
46 case int:
47 fmt.Printf("\nInteger: %d\n", v)
48 case string:
49 fmt.Printf("\nString: %s\n", v)
50 case bool:
51 fmt.Printf("\nBoolean: %t\n", v)
52 default:
53 fmt.Printf("\nUnknown type: %T\n", v)
54 }
55
56 // Fallthrough (explicit, unlike C)
57 score := 85
58 fmt.Printf("\nScore: %d\n", score)
59
60 switch {
61 case score >= 90:
62 fmt.Println("Excellent")
63 case score >= 80:
64 fmt.Println("Good")
65 fallthrough // Explicitly continue to next case
66 case score >= 70:
67 fmt.Println("Passed with credit")
68 default:
69 fmt.Println("Needs improvement")
70 }
71}
Practical Examples - Control Flow in Action
If Statements with Initialization
Go's if can initialize variables right in the condition, keeping scope limited and code clean.
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Initialize variable and check condition in one statement
8 if user := getCurrentUser(); user != nil {
9 fmt.Printf("Welcome back, %s!\n", user.Name)
10 } else {
11 fmt.Println("Please log in to continue")
12 }
13}
14
15func getCurrentUser() *User {
16 // Simulate database lookup
17 return &User{Name: "Alice", ID: 123}
18}
19
20type User struct {
21 Name string
22 ID int
23}
Why This Pattern Matters:
- Scope Limitation: Variable only exists where needed
- Error Handling: Perfect for checking function results inline
- Clean Code: No temporary variables cluttering outer scope
Advanced If-Statement Patterns:
1// run
2package main
3
4import (
5 "fmt"
6 "math/rand"
7 "time"
8)
9
10func main() {
11 rand.Seed(time.Now().UnixNano())
12
13 // Pattern 1: Error checking with initialization
14 if err := validateData(rand.Intn(100)); err != nil {
15 fmt.Printf("Validation failed: %v\n", err)
16 } else {
17 fmt.Println("Data is valid")
18 }
19
20 // Pattern 2: Multiple return values
21 if value, ok := tryGetValue(); ok {
22 fmt.Printf("Retrieved value: %d\n", value)
23 } else {
24 fmt.Println("Value not available")
25 }
26
27 // Pattern 3: Complex condition with initialization
28 if result := computeExpensiveValue(); result > 50 && result < 100 {
29 fmt.Printf("Result %d is in acceptable range\n", result)
30 } else {
31 fmt.Printf("Result %d is out of range\n", result)
32 }
33
34 // Pattern 4: Nested if with scope management
35 if config := loadConfig(); config != nil {
36 if config.Enabled {
37 fmt.Println("Feature is enabled")
38 if config.Level > 5 {
39 fmt.Println("Advanced features available")
40 }
41 }
42 }
43}
44
45func validateData(value int) error {
46 if value < 0 || value > 100 {
47 return fmt.Errorf("value %d out of range", value)
48 }
49 return nil
50}
51
52func tryGetValue() (int, bool) {
53 if rand.Float64() > 0.5 {
54 return rand.Intn(100), true
55 }
56 return 0, false
57}
58
59func computeExpensiveValue() int {
60 // Simulate expensive computation
61 return rand.Intn(150)
62}
63
64type Config struct {
65 Enabled bool
66 Level int
67}
68
69func loadConfig() *Config {
70 return &Config{Enabled: true, Level: 7}
71}
For Loops: All Forms Covered
Go's single for loop handles every looping scenario efficiently.
Form 1: Classic C-style Loop
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Count from 0 to 9
8 for i := 0; i < 10; i++ {
9 fmt.Printf("Count: %d\n", i)
10 }
11}
When to Use: When you know exact number of iterations and need index access.
Form 2: While-style Loop
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Read until user enters "quit"
8 input := ""
9 for input != "quit" {
10 fmt.Print("Enter command: ")
11 fmt.Scanln(&input)
12 fmt.Printf("You entered: %s\n", input)
13 }
14 fmt.Println("Goodbye!")
15}
When to Use: When you don't know how many iterations but know when to stop.
Form 3: Infinite Loop with Explicit Exit
1// run
2package main
3
4import (
5 "fmt"
6 "time"
7 "math/rand"
8)
9
10func main() {
11 // Game loop - run until player wins or quits
12 score := 0
13 for {
14 fmt.Printf("Current score: %d\n", score)
15
16 // Simulate game action
17 action := rand.Intn(3)
18 switch action {
19 case 0:
20 points := rand.Intn(10) + 1
21 score += points
22 fmt.Printf("You gained %d points!\n", points)
23 case 1:
24 fmt.Println("Nothing happened this turn")
25 case 2:
26 if score > 0 {
27 loss := rand.Intn(score) + 1
28 score -= loss
29 fmt.Printf("You lost %d points!\n", loss)
30 }
31 }
32
33 if score >= 50 {
34 fmt.Printf("Congratulations! You reached %d points and won!\n", score)
35 break
36 }
37
38 time.Sleep(500 * time.Millisecond)
39 }
40}
When to Use: For continuous processes like servers, game loops, or monitoring systems.
Advanced Loop Patterns:
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Pattern 1: Loop with multiple variables
8 fmt.Println("Fibonacci sequence:")
9 for a, b := 0, 1; a < 100; a, b = b, a+b {
10 fmt.Printf("%d ", a)
11 }
12 fmt.Println()
13
14 // Pattern 2: Labeled loops for breaking nested loops
15 fmt.Println("\nBreaking nested loops:")
16outer:
17 for i := 0; i < 3; i++ {
18 for j := 0; j < 3; j++ {
19 fmt.Printf("[%d,%d] ", i, j)
20 if i == 1 && j == 1 {
21 fmt.Println("\nBreaking out of both loops")
22 break outer // Breaks from outer loop
23 }
24 }
25 }
26
27 // Pattern 3: Loop with continue to specific label
28 fmt.Println("\nContinue with label:")
29outer2:
30 for i := 0; i < 3; i++ {
31 for j := 0; j < 3; j++ {
32 if j == 1 {
33 continue outer2 // Continue outer loop
34 }
35 fmt.Printf("[%d,%d] ", i, j)
36 }
37 }
38 fmt.Println()
39
40 // Pattern 4: Post-condition loop simulation
41 fmt.Println("\nDo-while style loop:")
42 counter := 0
43 for {
44 fmt.Printf("Counter: %d\n", counter)
45 counter++
46
47 if counter >= 3 {
48 break
49 }
50 }
51}
Range Iteration: Collection Processing
range is Go's elegant solution for iterating over collections safely and efficiently.
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Process different collection types
8 fruits := []string{"apple", "banana", "cherry"}
9
10 fmt.Println("Processing fruits:")
11 for i, fruit := range fruits {
12 fmt.Printf("%d: %s\n", i+1, fruit)
13 }
14
15 // Map iteration
16 prices := map[string]float64{
17 "apple": 0.99,
18 "banana": 0.59,
19 "cherry": 2.99,
20 }
21
22 fmt.Println("\nPrice list:")
23 for fruit, price := range prices {
24 fmt.Printf("%s: $%.2f\n", fruit, price)
25 }
26
27 // String iteration
28 word := "hello"
29 fmt.Println("\nString characters:")
30 for i, char := range word {
31 fmt.Printf("%d: %c\n", i, char)
32 }
33}
Range Deep Dive:
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Range on slices - returns index and value
8 numbers := []int{10, 20, 30, 40, 50}
9
10 fmt.Println("Full range:")
11 for i, v := range numbers {
12 fmt.Printf("Index %d: Value %d\n", i, v)
13 }
14
15 // Ignore index with blank identifier
16 fmt.Println("\nValues only:")
17 for _, v := range numbers {
18 fmt.Printf("%d ", v)
19 }
20 fmt.Println()
21
22 // Ignore value (just iterate indices)
23 fmt.Println("\nIndices only:")
24 for i := range numbers {
25 fmt.Printf("%d ", i)
26 }
27 fmt.Println()
28
29 // Range on maps - order is random
30 fmt.Println("\nMap iteration (random order):")
31 m := map[string]int{"a": 1, "b": 2, "c": 3}
32 for key, value := range m {
33 fmt.Printf("%s=%d ", key, value)
34 }
35 fmt.Println()
36
37 // Range on strings - yields runes (Unicode code points)
38 fmt.Println("\nString iteration (runes):")
39 text := "Hello, 世界"
40 for i, r := range text {
41 fmt.Printf("Byte %d: '%c' (U+%04X)\n", i, r, r)
42 }
43
44 // Range on channels
45 fmt.Println("\nChannel iteration:")
46 ch := make(chan int, 3)
47 ch <- 1
48 ch <- 2
49 ch <- 3
50 close(ch)
51
52 for value := range ch {
53 fmt.Printf("Received: %d\n", value)
54 }
55}
Switch Statements: Clean Multi-way Branching
Switch statements provide cleaner alternatives to long if-else chains.
1// run
2package main
3
4import "fmt"
5
6func main() {
7 // Grade calculator using switch
8 score := 85
9
10 switch {
11 case score >= 90:
12 fmt.Println("Grade: A - Excellent!")
13 case score >= 80:
14 fmt.Println("Grade: B - Good job!")
15 case score >= 70:
16 fmt.Println("Grade: C - You passed")
17 case score >= 60:
18 fmt.Println("Grade: D - Need improvement")
19 default:
20 fmt.Println("Grade: F - See me after class")
21 }
22
23 // Menu processor
24 fmt.Println("\nMenu:")
25 fmt.Println("1. Check balance")
26 fmt.Println("2. Deposit money")
27 fmt.Println("3. Withdraw money")
28 fmt.Println("4. Exit")
29
30 var choice int
31 fmt.Print("Enter your choice: ")
32 fmt.Scanln(&choice)
33
34 switch choice {
35 case 1:
36 checkBalance()
37 case 2:
38 depositMoney()
39 case 3:
40 withdrawMoney()
41 case 4:
42 fmt.Println("Thank you for banking with us!")
43 default:
44 fmt.Println("Invalid choice. Please try again.")
45 }
46}
47
48func checkBalance() {
49 fmt.Println("Your balance is $1,234.56")
50}
51
52func depositMoney() {
53 fmt.Println("Deposit feature coming soon!")
54}
55
56func withdrawMoney() {
57 fmt.Println("Withdrawal feature coming soon!")
58}
Defer: Guaranteed Cleanup
The defer statement ensures code runs when a function exits, regardless of how it exits.
1// run
2package main
3
4import "fmt"
5
6func main() {
7 demonstrateDefer()
8 demonstrateMultipleDefers()
9 demonstrateDeferWithPanic()
10}
11
12func demonstrateDefer() {
13 fmt.Println("\n=== Basic Defer ===")
14 defer fmt.Println("This runs last")
15 fmt.Println("This runs first")
16 fmt.Println("This runs second")
17}
18
19func demonstrateMultipleDefers() {
20 fmt.Println("\n=== Multiple Defers (LIFO) ===")
21 defer fmt.Println("Defer 1 (runs third)")
22 defer fmt.Println("Defer 2 (runs second)")
23 defer fmt.Println("Defer 3 (runs first)")
24 fmt.Println("Normal execution")
25}
26
27func demonstrateDeferWithPanic() {
28 fmt.Println("\n=== Defer with Panic Recovery ===")
29
30 defer func() {
31 if r := recover(); r != nil {
32 fmt.Printf("Recovered from panic: %v\n", r)
33 }
34 fmt.Println("Cleanup completed even after panic")
35 }()
36
37 fmt.Println("About to panic...")
38 // Uncommenting will demonstrate panic recovery
39 // panic("Something went wrong!")
40 fmt.Println("Continuing normally")
41}
Common Patterns and Pitfalls
Pattern 1: Input Validation Loop
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7)
8
9func main() {
10 // Keep asking until valid input received
11 for {
12 fmt.Print("Enter your age: ")
13 var age int
14 _, err := fmt.Scanln(&age)
15
16 if err != nil {
17 fmt.Println("Invalid input. Please enter a number.")
18 continue
19 }
20
21 if age >= 18 && age <= 120 {
22 fmt.Printf("Thank you! Your age %d is valid.\n", age)
23 break
24 }
25
26 fmt.Printf("Age %d is outside valid range. Please try again.\n", age)
27 }
28}
Pattern 2: Resource Cleanup with Defer
1// run
2package main
3
4import (
5 "fmt"
6 "os"
7)
8
9func main() {
10 processFile("/tmp/example.txt")
11}
12
13func processFile(filename string) {
14 // Open file
15 file, err := os.Open(filename)
16 if err != nil {
17 fmt.Printf("Error opening file: %v\n", err)
18 return
19 }
20
21 // Ensure file is closed even if panic occurs
22 defer func() {
23 fmt.Printf("Closing file: %s\n", filename)
24 file.Close()
25 }()
26
27 // Process file content
28 fmt.Printf("Successfully opened file: %s\n", filename)
29
30 // Simulate potential panic
31 // panic("Something went wrong!")
32
33 fmt.Println("File processing completed")
34}
Common Pitfall 1: Modifying Slice During Range
1// ❌ WRONG - Modifying slice while iterating
2func removeEvenWrong(numbers []int) []int {
3 for i, num := range numbers {
4 if num%2 == 0 {
5 // Danger: Modifying slice during iteration!
6 numbers = append(numbers[:i], numbers[i+1:]...)
7 }
8 }
9 return numbers
10}
11
12// ✅ CORRECT - Create new slice
13func removeEvenCorrect(numbers []int) []int {
14 result := make([]int, 0, len(numbers))
15 for _, num := range numbers {
16 if num%2 != 0 {
17 result = append(result, num)
18 }
19 }
20 return result
21}
Common Pitfall 2: Forgetting Break in Switch
1// ❌ WRONG - Missing break causes fallthrough
2func processWeekdayWrong(day int) {
3 switch day {
4 case 1, 2, 3, 4, 5:
5 fmt.Println("Weekday")
6 case 6, 7:
7 fmt.Println("Weekend")
8 }
9 // No break needed in Go, but this shows the concept
10}
11
12// ✅ CORRECT - Go's switch doesn't fallthrough
13func processWeekdayCorrect(day int) {
14 switch day {
15 case 1, 2, 3, 4, 5:
16 fmt.Println("Weekday")
17 case 6, 7:
18 fmt.Println("Weekend")
19 default:
20 fmt.Println("Invalid day")
21 }
22}
Common Pitfall 3: Off-by-One Errors
1// ❌ WRONG - Off by one in loop bounds
2func processArrayWrong() {
3 arr := [5]int{10, 20, 30, 40, 50}
4
5 // Wrong: i <= len(arr) will panic on last iteration
6 for i := 0; i <= len(arr); i++ {
7 fmt.Printf("Element %d: %d\n", i, arr[i]) // Panic!
8 }
9}
10
11// ✅ CORRECT - Use < not <=
12func processArrayCorrect() {
13 arr := [5]int{10, 20, 30, 40, 50}
14
15 // Correct: i < len(arr)
16 for i := 0; i < len(arr); i++ {
17 fmt.Printf("Element %d: %d\n", i, arr[i])
18 }
19}
Integration and Mastery - Building Real Applications
Example 1: Data Processing Pipeline
1// run
2package main
3
4import (
5 "fmt"
6 "math/rand"
7 "time"
8)
9
10type Transaction struct {
11 ID int
12 Amount float64
13 Valid bool
14}
15
16func main() {
17 // Generate sample transactions
18 transactions := generateTransactions(1000)
19
20 // Process with different control flow patterns
21 validCount := 0
22 invalidCount := 0
23 totalAmount := 0.0
24
25 for _, tx := range transactions {
26 if tx.Valid {
27 validCount++
28 totalAmount += tx.Amount
29
30 // Categorize by amount
31 switch {
32 case tx.Amount > 1000:
33 fmt.Printf("TX %d: Large transaction $%.2f\n", tx.ID, tx.Amount)
34 case tx.Amount > 100:
35 fmt.Printf("TX %d: Medium transaction $%.2f\n", tx.ID, tx.Amount)
36 default:
37 fmt.Printf("TX %d: Small transaction $%.2f\n", tx.ID, tx.Amount)
38 }
39 } else {
40 invalidCount++
41 }
42 }
43
44 fmt.Printf("\nSummary:\n")
45 fmt.Printf("Valid transactions: %d\n", validCount)
46 fmt.Printf("Invalid transactions: %d\n", invalidCount)
47 fmt.Printf("Total amount: $%.2f\n", totalAmount)
48}
49
50func generateTransactions(count int) []Transaction {
51 transactions := make([]Transaction, count)
52
53 for i := 0; i < count; i++ {
54 transactions[i] = Transaction{
55 ID: i + 1,
56 Amount: rand.Float64() * 2000,
57 Valid: rand.Float64() > 0.1, // 90% valid
58 }
59 }
60
61 return transactions
62}
Example 2: Game Loop with State Management
1// run
2package main
3
4import (
5 "fmt"
6 "time"
7 "math/rand"
8)
9
10type GameState int
11
12const (
13 StateMenu GameState = iota
14 StatePlaying
15 StatePaused
16 StateGameOver
17)
18
19type Game struct {
20 State GameState
21 Score int
22 Level int
23 Lives int
24 IsRunning bool
25}
26
27func main() {
28 game := &Game{
29 State: StateMenu,
30 Score: 0,
31 Level: 1,
32 Lives: 3,
33 IsRunning: true,
34 }
35
36 // Main game loop
37 for game.IsRunning {
38 switch game.State {
39 case StateMenu:
40 handleMenu(game)
41 case StatePlaying:
42 handlePlaying(game)
43 case StatePaused:
44 handlePaused(game)
45 case StateGameOver:
46 handleGameOver(game)
47 }
48
49 time.Sleep(100 * time.Millisecond)
50 }
51
52 fmt.Println("Game ended. Thanks for playing!")
53}
54
55func handleMenu(game *Game) {
56 fmt.Println("\n=== SPACE ADVENTURE ===")
57 fmt.Println("1. Start Game")
58 fmt.Println("2. Quit")
59 fmt.Print("Choose option: ")
60
61 var choice int
62 fmt.Scanln(&choice)
63
64 if choice == 1 {
65 game.State = StatePlaying
66 fmt.Println("Starting game...")
67 } else {
68 game.IsRunning = false
69 }
70}
71
72func handlePlaying(game *Game) {
73 // Simulate game events
74 event := rand.Intn(10)
75
76 switch {
77 case event < 3: // Score points
78 points := rand.Intn(100) + 10
79 game.Score += points
80 fmt.Printf("You scored %d points! Total: %d\n", points, game.Score)
81
82 // Check level progression
83 if game.Score > game.Level*500 {
84 game.Level++
85 game.Lives++
86 fmt.Printf("Level %d reached! Bonus life!\n", game.Level)
87 }
88
89 case event < 6: // Take damage
90 if game.Lives > 0 {
91 game.Lives--
92 fmt.Printf("Ouch! Lives remaining: %d\n", game.Lives)
93
94 if game.Lives == 0 {
95 game.State = StateGameOver
96 }
97 }
98
99 case event < 8: // Find power-up
100 fmt.Println("Power-up found! Extra points!")
101 game.Score += 200
102
103 default: // Nothing happens
104 // Continue playing
105 }
106
107 // Random pause
108 if rand.Float64() < 0.05 {
109 fmt.Println("Game paused")
110 game.State = StatePaused
111 }
112}
113
114func handlePaused(game *Game) {
115 // Simple pause handling
116 fmt.Println("Game resumed")
117 game.State = StatePlaying
118}
119
120func handleGameOver(game *Game) {
121 fmt.Printf("\n=== GAME OVER ===\n")
122 fmt.Printf("Final Score: %d\n", game.Score)
123 fmt.Printf("Levels Reached: %d\n", game.Level)
124
125 fmt.Println("\n1. Play Again")
126 fmt.Println("2. Quit")
127 fmt.Print("Choose option: ")
128
129 var choice int
130 fmt.Scanln(&choice)
131
132 if choice == 1 {
133 // Reset game
134 game.Score = 0
135 game.Level = 1
136 game.Lives = 3
137 game.State = StateMenu
138 } else {
139 game.IsRunning = false
140 }
141}
Performance Considerations
Loop Optimization Tips
1// run
2package main
3
4import (
5 "fmt"
6 "time"
7)
8
9func main() {
10 // Test different loop patterns
11 data := make([]int, 1000000)
12 for i := range data {
13 data[i] = i
14 }
15
16 // Pattern 1: Standard range
17 start := time.Now()
18 sum1 := 0
19 for _, v := range data {
20 sum1 += v
21 }
22 fmt.Printf("Range loop: %v, Sum: %d\n", time.Since(start), sum1)
23
24 // Pattern 2: Index access
25 start = time.Now()
26 sum2 := 0
27 for i := 0; i < len(data); i++ {
28 sum2 += data[i]
29 }
30 fmt.Printf("Index loop: %v, Sum: %d\n", time.Since(start), sum2)
31
32 // Pattern 3: Avoid repeated len() calls
33 start = time.Now()
34 sum3 := 0
35 n := len(data) // Cache length
36 for i := 0; i < n; i++ {
37 sum3 += data[i]
38 }
39 fmt.Printf("Cached length loop: %v, Sum: %d\n", time.Since(start), sum3)
40}
Early Exit Patterns
1// run
2package main
3
4import (
5 "fmt"
6 "math"
7)
8
9func main() {
10 // Find first prime number greater than 1000
11 for i := 1001; ; i++ {
12 if isPrime(i) {
13 fmt.Printf("First prime > 1000: %d\n", i)
14 break // Exit early - no need to continue
15 }
16 }
17}
18
19func isPrime(n int) bool {
20 if n < 2 {
21 return false
22 }
23 if n == 2 {
24 return true
25 }
26 if n%2 == 0 {
27 return false
28 }
29
30 // Only check up to sqrt(n)
31 limit := int(math.Sqrt(float64(n)))
32 for i := 3; i <= limit; i += 2 {
33 if n%i == 0 {
34 return false
35 }
36 }
37
38 return true
39}
Practice Exercises
Exercise 1: Temperature Converter
🎯 Learning Objectives: Master switch statements and conditional logic for handling multiple conversion types with user input validation.
🌍 Real-World Context: Think of a weather application that needs to convert between different temperature units for international users - proper control flow ensures accurate conversions and meaningful error messages.
⭐ Difficulty: Beginner | ⏱️ Time Estimate: 15 minutes
Write a program that converts temperatures between Celsius and Fahrenheit based on user input.
Solution
1// run
2package main
3
4import "fmt"
5
6func main() {
7 tempType := "C" // C for Celsius, F for Fahrenheit
8 value := 25.0
9
10 switch tempType {
11 case "C":
12 fahrenheit := value*9/5 + 32
13 fmt.Printf("%.1f°C = %.1f°F\n", value, fahrenheit)
14 case "F":
15 celsius := (value - 32) * 5 / 9
16 fmt.Printf("%.1f°F = %.1f°C\n", value, celsius)
17 default:
18 fmt.Println("Invalid temperature type")
19 }
20}
Exercise 2: Sum of Even Numbers
🎯 Learning Objectives: Practice for loop patterns with conditional statements and flow control using continue statements.
🌍 Real-World Context: Consider a billing system that processes only even-numbered invoice IDs for a specific batch - control flow statements help filter and process the right subset of data efficiently.
⭐ Difficulty: Beginner | ⏱️ Time Estimate: 15 minutes
Write a program that calculates the sum of all even numbers from 1 to 100.
Solution
1// run
2package main
3
4import "fmt"
5
6func main() {
7 sum := 0
8
9 for i := 1; i <= 100; i++ {
10 if i%2 != 0 {
11 continue // Skip odd numbers
12 }
13 sum += i
14 }
15
16 fmt.Printf("Sum of even numbers from 1 to 100: %d\n", sum)
17}
Exercise 3: Prime Number Checker
🎯 Learning Objectives: Implement efficient algorithms using loops, conditional logic, and early termination with break statements.
🌍 Real-World Context: Think of a cryptographic system that needs to validate prime numbers for key generation - efficient prime checking with early exit improves performance significantly.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 20 minutes
Write a function that checks if a number is prime.
Solution
1// run
2package main
3
4import "fmt"
5
6func isPrime(n int) bool {
7 if n < 2 {
8 return false
9 }
10
11 for i := 2; i*i <= n; i++ {
12 if n%i == 0 {
13 return false
14 }
15 }
16
17 return true
18}
19
20func main() {
21 numbers := []int{2, 3, 4, 17, 20, 29, 100}
22
23 for _, num := range numbers {
24 if isPrime(num) {
25 fmt.Printf("%d is prime\n", num)
26 } else {
27 fmt.Printf("%d is not prime\n", num)
28 }
29 }
30}
Exercise 4: Word Counter
🎯 Learning Objectives: Master range loops over maps and string manipulation for data analysis and frequency counting.
🌍 Real-World Context: Consider a text analysis tool that counts word frequencies for SEO optimization or content analysis. Map iteration and range loops provide efficient data aggregation.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 20 minutes
Count the frequency of each word in a sentence using range and map.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7)
8
9func main() {
10 sentence := "go is fun go is powerful go is simple"
11 words := strings.Fields(sentence)
12
13 frequency := make(map[string]int)
14
15 for _, word := range words {
16 frequency[word]++
17 }
18
19 for word, count := range frequency {
20 fmt.Printf("%s: %d\n", word, count)
21 }
22}
Exercise 5: Pattern Printer
🎯 Learning Objectives: Implement nested loops and pattern generation algorithms for complex visual output formatting.
🌍 Real-World Context: Think of a UI component library that needs to generate various visual patterns for progress indicators or loading animations. Nested loops provide the foundation for such visual programming.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 25 minutes
Create a program that prints a pyramid pattern of asterisks.
Solution
1// run
2package main
3
4import "fmt"
5
6func main() {
7 height := 5
8
9 for i := 1; i <= height; i++ {
10 // Print spaces
11 for j := 0; j < height-i; j++ {
12 fmt.Print(" ")
13 }
14
15 // Print asterisks
16 for k := 0; k < 2*i-1; k++ {
17 fmt.Print("*")
18 }
19
20 fmt.Println()
21 }
22}
Exercise 6: FizzBuzz
🎯 Learning Objectives: Practice conditional logic and multiple branching scenarios with modulo operations for classic algorithmic problems.
🌍 Real-World Context: Consider a game development system that needs to handle multiple game state conditions simultaneously. FizzBuzz teaches pattern recognition and multi-conditional logic essential for game mechanics.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 20 minutes
Write a program that prints numbers from 1 to 100. For multiples of 3, print "Fizz"; for multiples of 5, print "Buzz"; for multiples of both, print "FizzBuzz".
Solution
1// run
2package main
3
4import "fmt"
5
6func fizzBuzz(n int) string {
7 if n%15 == 0 {
8 return "FizzBuzz"
9 } else if n%3 == 0 {
10 return "Fizz"
11 } else if n%5 == 0 {
12 return "Buzz"
13 }
14 return fmt.Sprintf("%d", n)
15}
16
17func main() {
18 for i := 1; i <= 100; i++ {
19 fmt.Println(fizzBuzz(i))
20 }
21}
Exercise 7: Grade Calculator
🎯 Learning Objectives: Implement complex switch statements with expressions and range-based logic for grading systems.
🌍 Real-World Context: Think of an educational platform that automatically converts numeric scores to letter grades. Switch expressions provide clean, readable logic for multi-level categorization.
⭐ Difficulty: Intermediate | ⏱️ Time Estimate: 25 minutes
Create a function that takes a numerical score and returns a letter grade using switch statements.
Solution
1// run
2package main
3
4import "fmt"
5
6func getLetterGrade(score int) string {
7 switch {
8 case score >= 90:
9 return "A"
10 case score >= 80:
11 return "B"
12 case score >= 70:
13 return "C"
14 case score >= 60:
15 return "D"
16 default:
17 return "F"
18 }
19}
20
21func main() {
22 scores := []int{95, 87, 82, 76, 73, 68, 55, 42}
23
24 for _, score := range scores {
25 grade := getLetterGrade(score)
26 fmt.Printf("Score %d: %s\n", score, grade)
27 }
28}
Exercise 8: Number Pyramid
🎯 Learning Objectives: Master complex nested loop patterns and string manipulation for creating structured numeric patterns.
🌍 Real-World Context: Consider a data visualization tool that creates hierarchical tree structures or pyramid charts. Nested loops with proper spacing control create the foundation for such visual algorithms.
⭐ Difficulty: Advanced | ⏱️ Time Estimate: 30 minutes
Write a program that prints a number pyramid using nested loops.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7)
8
9func printNumberPyramid(rows int) {
10 for i := 1; i <= rows; i++ {
11 // Print spaces
12 spaces := strings.Repeat(" ", rows-i)
13 fmt.Print(spaces)
14
15 // Print ascending numbers
16 for j := 1; j <= i; j++ {
17 fmt.Print(j)
18 }
19
20 // Print descending numbers
21 for j := i - 1; j >= 1; j-- {
22 fmt.Print(j)
23 }
24
25 fmt.Println()
26 }
27}
28
29func main() {
30 fmt.Println("Number Pyramid:")
31 printNumberPyramid(5)
32}
Exercise 9: Prime Number Finder
🎯 Learning Objectives: Implement efficient algorithms for prime number generation using Sieve of Eratosthenes and optimization techniques.
🌍 Real-World Context: Think of a cryptographic system that needs to generate prime numbers for RSA key pairs. Efficient prime finding algorithms are crucial for security system performance.
⭐ Difficulty: Advanced | ⏱️ Time Estimate: 35 minutes
Find all prime numbers up to a given number using loops and conditional logic.
Solution
1// run
2package main
3
4import "fmt"
5
6func isPrime(n int) bool {
7 if n < 2 {
8 return false
9 }
10 if n == 2 {
11 return true
12 }
13 if n%2 == 0 {
14 return false
15 }
16
17 // Check odd divisors up to sqrt(n)
18 for i := 3; i*i <= n; i += 2 {
19 if n%i == 0 {
20 return false
21 }
22 }
23
24 return true
25}
26
27func findPrimes(limit int) []int {
28 primes := []int{}
29 for i := 2; i <= limit; i++ {
30 if isPrime(i) {
31 primes = append(primes, i)
32 }
33 }
34 return primes
35}
36
37func main() {
38 limit := 50
39
40 fmt.Printf("Prime numbers up to %d:\n", limit)
41 primes := findPrimes(limit)
42 fmt.Println(primes)
43
44 fmt.Printf("\nTotal primes found: %d\n", len(primes))
45}
Exercise 10: Menu-Driven Calculator
🎯 Learning Objectives: Build interactive applications combining loops, switch statements, and user input handling for menu-driven interfaces.
🌍 Real-World Context: Consider a command-line financial calculator that provides multiple calculation modes. Menu-driven interfaces with proper control flow are essential for user-friendly CLI applications.
⭐ Difficulty: Advanced | ⏱️ Time Estimate: 40 minutes
Create an interactive calculator with a menu system using switch and loops.
Solution
1// run
2package main
3
4import (
5 "bufio"
6 "fmt"
7 "os"
8 "strconv"
9 "strings"
10)
11
12func displayMenu() {
13 fmt.Println("\n=== Calculator Menu ===")
14 fmt.Println("1. Add")
15 fmt.Println("2. Subtract")
16 fmt.Println("3. Multiply")
17 fmt.Println("4. Divide")
18 fmt.Println("5. Power")
19 fmt.Println("6. Square Root")
20 fmt.Println("0. Exit")
21 fmt.Print("Enter choice: ")
22}
23
24func getFloat(prompt string) (float64, error) {
25 reader := bufio.NewReader(os.Stdin)
26 fmt.Print(prompt)
27 input, _ := reader.ReadString('\n')
28 input = strings.TrimSpace(input)
29 return strconv.ParseFloat(input, 64)
30}
31
32func power(base, exp float64) float64 {
33 result := 1.0
34 absExp := exp
35 if exp < 0 {
36 absExp = -exp
37 }
38
39 for i := 0; i < int(absExp); i++ {
40 result *= base
41 }
42
43 if exp < 0 {
44 return 1.0 / result
45 }
46 return result
47}
48
49func sqrt(n float64) float64 {
50 if n < 0 {
51 return 0
52 }
53 if n == 0 {
54 return 0
55 }
56
57 // Newton's method
58 guess := n / 2
59 for i := 0; i < 10; i++ {
60 guess = (guess + n/guess) / 2
61 }
62 return guess
63}
64
65func main() {
66 reader := bufio.NewReader(os.Stdin)
67
68 for {
69 displayMenu()
70
71 input, _ := reader.ReadString('\n')
72 choice := strings.TrimSpace(input)
73
74 if choice == "0" {
75 fmt.Println("Goodbye!")
76 break
77 }
78
79 var num1, num2 float64
80 var err error
81
82 switch choice {
83 case "1", "2", "3", "4", "5":
84 num1, err = getFloat("Enter first number: ")
85 if err != nil {
86 fmt.Println("Invalid input")
87 continue
88 }
89
90 num2, err = getFloat("Enter second number: ")
91 if err != nil {
92 fmt.Println("Invalid input")
93 continue
94 }
95
96 case "6":
97 num1, err = getFloat("Enter number: ")
98 if err != nil {
99 fmt.Println("Invalid input")
100 continue
101 }
102
103 default:
104 fmt.Println("Invalid choice")
105 continue
106 }
107
108 switch choice {
109 case "1":
110 fmt.Printf("Result: %.2f + %.2f = %.2f\n", num1, num2, num1+num2)
111 case "2":
112 fmt.Printf("Result: %.2f - %.2f = %.2f\n", num1, num2, num1-num2)
113 case "3":
114 fmt.Printf("Result: %.2f × %.2f = %.2f\n", num1, num2, num1*num2)
115 case "4":
116 if num2 == 0 {
117 fmt.Println("Error: Division by zero")
118 } else {
119 fmt.Printf("Result: %.2f ÷ %.2f = %.2f\n", num1, num2, num1/num2)
120 }
121 case "5":
122 result := power(num1, num2)
123 fmt.Printf("Result: %.2f ^ %.2f = %.2f\n", num1, num2, result)
124 case "6":
125 if num1 < 0 {
126 fmt.Println("Error: Cannot calculate square root of negative number")
127 } else {
128 result := sqrt(num1)
129 fmt.Printf("Result: √%.2f = %.2f\n", num1, result)
130 }
131 }
132 }
133}
Exercise 11: Data Pipeline Processor
🎯 Learning Objectives: Implement data processing pipelines with conditional filtering, transformation, and aggregation using control flow structures.
🌍 Real-World Context: Think of an ETL system that processes streaming data. Control flow structures enable flexible data filtering and transformation pipelines.
⭐ Difficulty: Advanced | ⏱️ Time Estimate: 45 minutes
Create a data processing pipeline that filters, transforms, and aggregates data using various control flow patterns.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "math"
7 "strings"
8)
9
10type DataRecord struct {
11 ID int
12 Name string
13 Value float64
14 Status string
15 Category string
16}
17
18type ProcessedData struct {
19 Total int
20 ValidCount int
21 SumValue float64
22 AvgValue float64
23 MaxValue float64
24 MinValue float64
25 CategoryCounts map[string]int
26 StatusCounts map[string]int
27 ValidRecords []DataRecord
28}
29
30func processData(records []DataRecord) ProcessedData {
31 result := ProcessedData{
32 CategoryCounts: make(map[string]int),
33 StatusCounts: make(map[string]int),
34 ValidRecords: make([]DataRecord, 0),
35 MinValue: math.MaxFloat64,
36 }
37
38 for _, record := range records {
39 result.Total++
40
41 // Skip invalid records
42 if record.Name == "" || record.Value < 0 {
43 continue
44 }
45
46 // Process valid records
47 result.ValidCount++
48 result.SumValue += record.Value
49 result.CategoryCounts[record.Category]++
50 result.StatusCounts[record.Status]++
51
52 // Update min/max
53 if record.Value > result.MaxValue {
54 result.MaxValue = record.Value
55 }
56 if record.Value < result.MinValue {
57 result.MinValue = record.Value
58 }
59
60 // Apply transformations
61 if strings.Contains(record.Name, "premium") {
62 record.Value *= 1.2 // Premium boost
63 }
64
65 result.ValidRecords = append(result.ValidRecords, record)
66 }
67
68 // Calculate average
69 if result.ValidCount > 0 {
70 result.AvgValue = result.SumValue / float64(result.ValidCount)
71 } else {
72 result.MinValue = 0
73 }
74
75 return result
76}
77
78func main() {
79 // Sample data
80 records := []DataRecord{
81 {1, "premium product", 100.0, "active", "electronics"},
82 {2, "basic service", 25.0, "active", "software"},
83 {3, "", 50.0, "inactive", "hardware"}, // Invalid
84 {4, "premium plan", 75.0, "pending", "software"},
85 {5, "standard item", 30.0, "active", "hardware"},
86 {6, "enterprise solution", 200.0, "active", "software"},
87 {7, "basic tool", -10.0, "active", "hardware"}, // Invalid
88 {8, "premium widget", 85.0, "expired", "electronics"},
89 {9, "starter pack", 15.0, "active", "software"},
90 {10, "pro service", 120.0, "active", "electronics"},
91 }
92
93 fmt.Println("=== Data Pipeline Processing Demo ===\n")
94
95 // Process all data
96 fmt.Println("1. Overall Processing:")
97 result := processData(records)
98 fmt.Printf(" Total records: %d\n", result.Total)
99 fmt.Printf(" Valid records: %d\n", result.ValidCount)
100 fmt.Printf(" Average value: %.2f\n", result.AvgValue)
101 fmt.Printf(" Value range: %.2f - %.2f\n", result.MinValue, result.MaxValue)
102 fmt.Printf(" Categories: %v\n", result.CategoryCounts)
103 fmt.Printf(" Statuses: %v\n", result.StatusCounts)
104}
Exercise 12: State Machine Implementation
🎯 Learning Objectives: Implement state machines with complex state transitions and event handling using switch statements and control flow.
🌍 Real-World Context: Consider a traffic light controller or order processing system. State machines provide reliable patterns for managing complex state-dependent behavior.
⭐ Difficulty: Advanced | ⏱️ Time Estimate: 50 minutes
Build a state machine for an order processing system with multiple states and transitions.
Solution
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7 "time"
8)
9
10type OrderState int
11
12const (
13 StatePending OrderState = iota
14 StateConfirmed
15 StateProcessing
16 StateShipped
17 StateDelivered
18 StateCancelled
19 StateRefunded
20)
21
22type OrderEvent int
23
24const (
25 EventConfirm OrderEvent = iota
26 EventStartProcessing
27 EventShip
28 EventDeliver
29 EventCancel
30 EventRefund
31 EventReturn
32)
33
34type Order struct {
35 ID int
36 State OrderState
37 Total float64
38 Customer string
39 CreatedAt time.Time
40 UpdatedAt time.Time
41 History []StateTransition
42}
43
44type StateTransition struct {
45 From OrderState
46 To OrderState
47 Event OrderEvent
48 Timestamp time.Time
49 Note string
50}
51
52type StateMachine struct {
53 allowedTransitions map[OrderState]map[OrderEvent]OrderState
54}
55
56func NewStateMachine() *StateMachine {
57 sm := &StateMachine{
58 allowedTransitions: make(map[OrderState]map[OrderEvent]OrderState),
59 }
60
61 // Define valid transitions
62 sm.addTransition(StatePending, EventConfirm, StateConfirmed)
63 sm.addTransition(StatePending, EventCancel, StateCancelled)
64
65 sm.addTransition(StateConfirmed, EventStartProcessing, StateProcessing)
66 sm.addTransition(StateConfirmed, EventCancel, StateCancelled)
67
68 sm.addTransition(StateProcessing, EventShip, StateShipped)
69 sm.addTransition(StateProcessing, EventCancel, StateCancelled)
70
71 sm.addTransition(StateShipped, EventDeliver, StateDelivered)
72 sm.addTransition(StateShipped, EventReturn, StateProcessing)
73
74 sm.addTransition(StateDelivered, EventReturn, StateRefunded)
75 sm.addTransition(StateDelivered, EventRefund, StateRefunded)
76
77 sm.addTransition(StateCancelled, EventRefund, StateRefunded)
78
79 return sm
80}
81
82func (sm *StateMachine) addTransition(from OrderState, event OrderEvent, to OrderState) {
83 if sm.allowedTransitions[from] == nil {
84 sm.allowedTransitions[from] = make(map[OrderEvent]OrderState)
85 }
86 sm.allowedTransitions[from][event] = to
87}
88
89func (sm *StateMachine) CanTransition(order *Order, event OrderEvent) bool {
90 if transitions, exists := sm.allowedTransitions[order.State]; exists {
91 if _, valid := transitions[event]; valid {
92 return true
93 }
94 }
95 return false
96}
97
98func (sm *StateMachine) Transition(order *Order, event OrderEvent, note string) error {
99 if !sm.CanTransition(order, event) {
100 return fmt.Errorf("invalid transition from %v with event %v", order.State, event)
101 }
102
103 newState := sm.allowedTransitions[order.State][event]
104
105 // Record transition
106 transition := StateTransition{
107 From: order.State,
108 To: newState,
109 Event: event,
110 Timestamp: time.Now(),
111 Note: note,
112 }
113
114 order.History = append(order.History, transition)
115 order.State = newState
116 order.UpdatedAt = time.Now()
117
118 return nil
119}
120
121func (o *Order) GetStateName() string {
122 switch o.State {
123 case StatePending:
124 return "Pending"
125 case StateConfirmed:
126 return "Confirmed"
127 case StateProcessing:
128 return "Processing"
129 case StateShipped:
130 return "Shipped"
131 case StateDelivered:
132 return "Delivered"
133 case StateCancelled:
134 return "Cancelled"
135 case StateRefunded:
136 return "Refunded"
137 default:
138 return "Unknown"
139 }
140}
141
142func (e OrderEvent) GetEventName() string {
143 switch e {
144 case EventConfirm:
145 return "Confirm"
146 case EventStartProcessing:
147 return "Start Processing"
148 case EventShip:
149 return "Ship"
150 case EventDeliver:
151 return "Deliver"
152 case EventCancel:
153 return "Cancel"
154 case EventRefund:
155 return "Refund"
156 case EventReturn:
157 return "Return"
158 default:
159 return "Unknown"
160 }
161}
162
163func processOrderWorkflow(sm *StateMachine, order *Order) {
164 fmt.Printf("\n=== Processing Order %d ===\n", order.ID)
165 fmt.Printf("Customer: %s\n", order.Customer)
166 fmt.Printf("Total: $%.2f\n", order.Total)
167
168 // Simulate order processing workflow
169 events := []struct {
170 event OrderEvent
171 note string
172 delay time.Duration
173 }{
174 {EventConfirm, "Order confirmed by customer", 500 * time.Millisecond},
175 {EventStartProcessing, "Started processing in warehouse", 1000 * time.Millisecond},
176 {EventShip, "Order shipped with tracking", 800 * time.Millisecond},
177 {EventDeliver, "Order delivered successfully", 600 * time.Millisecond},
178 }
179
180 for _, step := range events {
181 time.Sleep(step.delay)
182
183 if err := sm.Transition(order, step.event, step.note); err != nil {
184 fmt.Printf("❌ Error: %v\n", err)
185 return
186 }
187
188 fmt.Printf("✓ %s → %s (%s)\n",
189 step.event.GetEventName(),
190 order.GetStateName(),
191 step.note)
192 }
193
194 // Show final state
195 fmt.Printf("\nFinal State: %s\n", order.GetStateName())
196}
197
198func main() {
199 sm := NewStateMachine()
200
201 // Process normal order
202 order1 := &Order{
203 ID: 1001,
204 State: StatePending,
205 Total: 129.99,
206 Customer: "John Doe",
207 CreatedAt: time.Now(),
208 UpdatedAt: time.Now(),
209 History: []StateTransition{},
210 }
211
212 processOrderWorkflow(sm, order1)
213
214 // Process order with cancellation
215 fmt.Println("\n" + strings.Repeat("=", 50))
216 order2 := &Order{
217 ID: 1002,
218 State: StatePending,
219 Total: 89.50,
220 Customer: "Jane Smith",
221 CreatedAt: time.Now(),
222 UpdatedAt: time.Now(),
223 History: []StateTransition{},
224 }
225
226 fmt.Printf("\n=== Processing Order %d ===\n", order2.ID)
227
228 // Confirm first
229 sm.Transition(order2, EventConfirm, "Order confirmed")
230 fmt.Printf("✓ %s → %s\n", EventConfirm.GetEventName(), order2.GetStateName())
231
232 // Then cancel
233 time.Sleep(500 * time.Millisecond)
234 sm.Transition(order2, EventCancel, "Customer requested cancellation")
235 fmt.Printf("✓ %s → %s\n", EventCancel.GetEventName(), order2.GetStateName())
236
237 // Try to refund
238 time.Sleep(300 * time.Millisecond)
239 sm.Transition(order2, EventRefund, "Processing refund")
240 fmt.Printf("✓ %s → %s\n", EventRefund.GetEventName(), order2.GetStateName())
241
242 fmt.Println("\n=== State Machine Demo Complete ===")
243}
Summary
Key Takeaways
- Go's Philosophy: "Less is more" - fewer constructs but more powerful
- If Statements: Use initialization for clean, scoped variables
- For Loops: One loop handles all cases
- Switch Statements: Clean multi-way branching without fallthrough by default
- Defer: Essential for reliable resource cleanup
- Range: Safe, efficient way to iterate over collections
- Performance: Go's control structures are highly optimized
- Pitfalls: Watch for slice modification, off-by-one, and scope issues
Best Practices
- Use
rangefor collection iteration - safer and more readable - Initialize variables in
ifwhen possible - limited scope - Always have exit conditions in infinite loops
- Use
switchfor multi-way branching - cleaner than long if-else chains deferresource cleanup - ensures reliability- Cache
len()calls in tight loops when it doesn't change - Break early when possible - improves performance
Next Steps
- Practice: Implement the exercises below to reinforce concepts
- Explore: Learn about goroutines and channels for concurrent control flow
- Apply: Use these patterns in your own projects
- Advanced: Study algorithms that heavily use control flow
Control flow is the foundation of all programming. Master these Go patterns and you'll write clean, efficient, and reliable code!