Go Course #3: Control Flow — if, switch and Loops
Introduction: controlling program flow in Go
Welcome to Part 3 of the Go course for beginners. In the previous articles you learned how to declare variables and work with data types. Now we are going to learn something fundamental: how to make your program make decisions and repeat actions.

Go takes a minimalist approach to control flow. Unlike other languages, Go only has one type of loop (the for loop), and its if statement has a unique feature: it can include an initialization statement. These design choices make Go code cleaner and more predictable.
By the end of this article you will be able to write programs that react to different conditions, select between multiple options with switch, and repeat tasks using different variants of the for loop.
The if/else statement with init statement
The if statement in Go works similarly to other languages, but it has a feature that makes it special: you can include a short initialization statement before the condition.
1package main
2
3import "fmt"
4
5func main() {
6 age := 20
7
8 // Basic if
9 if age >= 18 {
10 fmt.Println("You are an adult")
11 } else {
12 fmt.Println("You are a minor")
13 }
14
15 // if with init statement
16 if grade := 85; grade >= 90 {
17 fmt.Println("Excellent")
18 } else if grade >= 70 {
19 fmt.Println("Passed")
20 } else {
21 fmt.Println("Failed")
22 }
23 // grade does not exist outside the if block
24}
if init statement (like grade above) only exists within the if/else block. This helps keep the scope as narrow as possible, which is a Go best practice.
Unlike C or Java, Go does not use parentheses around the condition, but braces are mandatory even for a single line of code.
1// This does NOT compile in Go
2if age >= 18
3 fmt.Println("Adult")
4
5// This is correct
6if age >= 18 {
7 fmt.Println("Adult")
8}
Comparison and logical operators
To build conditions you need comparison and logical operators. Go supports the following:
| Operator | Meaning | Example |
|---|---|---|
== | Equal to | 5 == 5 is true |
!= | Not equal to | 5 != 3 is true |
> | Greater than | 10 > 7 is true |
< | Less than | 3 < 8 is true |
>= | Greater or equal | 5 >= 5 is true |
<= | Less or equal | 4 <= 3 is false |
Logical operators let you combine multiple conditions:
| Operator | Meaning | Example |
|---|---|---|
&& | Logical AND | true && false is false |
|| | Logical OR | true || false is true |
! | Logical NOT | !true is false |
1package main
2
3import "fmt"
4
5func main() {
6 age := 25
7 hasID := true
8
9 if age >= 18 && hasID {
10 fmt.Println("You can enter the event")
11 }
12
13 temperature := 35
14 if temperature < 0 || temperature > 40 {
15 fmt.Println("Extreme weather, stay home")
16 }
17}
Switch: elegant multi-way selection
The switch statement in Go is more powerful and flexible than in many other languages. It has three main variants.
Switch with expression: evaluates a variable against multiple values.
1package main
2
3import "fmt"
4
5func main() {
6 day := "tuesday"
7
8 switch day {
9 case "monday":
10 fmt.Println("Start of the week")
11 case "tuesday", "wednesday", "thursday":
12 fmt.Println("Midweek")
13 case "friday":
14 fmt.Println("Almost weekend")
15 case "saturday", "sunday":
16 fmt.Println("Weekend")
17 default:
18 fmt.Println("Invalid day")
19 }
20}
case has an implicit break. You do not need to write break at the end of each case like in C or Java. If you want execution to continue to the next case, use the fallthrough keyword.
Switch without condition: replaces long if/else if chains. It is very readable.
1package main
2
3import "fmt"
4
5func classifyGrade(grade int) string {
6 switch {
7 case grade >= 90:
8 return "Excellent"
9 case grade >= 80:
10 return "Very good"
11 case grade >= 70:
12 return "Passed"
13 case grade >= 60:
14 return "Sufficient"
15 default:
16 return "Failed"
17 }
18}
19
20func main() {
21 fmt.Println(classifyGrade(85)) // Very good
22 fmt.Println(classifyGrade(55)) // Failed
23}
Fallthrough: forces execution of the next case without evaluating its condition.
1package main
2
3import "fmt"
4
5func main() {
6 number := 5
7
8 switch {
9 case number > 0:
10 fmt.Println("Positive")
11 fallthrough
12 case number > -10:
13 fmt.Println("Greater than -10")
14 }
15 // Prints: Positive
16 // Greater than -10
17}
fallthrough with caution. It is uncommon in Go and can make code difficult to understand. Most Go programmers prefer to avoid it unless strictly necessary.
The for loop: the only loop in Go
Go has only one type of loop: the for loop. But it is versatile enough to cover all the cases that other languages handle with while, do-while, or foreach.
Classic for with initialization, condition, and increment:
1package main
2
3import "fmt"
4
5func main() {
6 // Classic for (like C/Java)
7 for i := 0; i < 5; i++ {
8 fmt.Println("Iteration:", i)
9 }
10}
While-style for — condition only:
1package main
2
3import "fmt"
4
5func main() {
6 counter := 10
7
8 for counter > 0 {
9 fmt.Println("Countdown:", counter)
10 counter--
11 }
12 fmt.Println("Liftoff!")
13}
Infinite for — no condition (exit with break):
1package main
2
3import (
4 "bufio"
5 "fmt"
6 "os"
7 "strings"
8)
9
10func main() {
11 scanner := bufio.NewScanner(os.Stdin)
12
13 for {
14 fmt.Print("Type something (or 'quit' to exit): ")
15 scanner.Scan()
16 text := scanner.Text()
17
18 if strings.ToLower(text) == "quit" {
19 fmt.Println("Goodbye!")
20 break
21 }
22 fmt.Println("You typed:", text)
23 }
24}
For range — iterate over slices, arrays, maps, and strings:
1package main
2
3import "fmt"
4
5func main() {
6 fruits := []string{"apple", "banana", "orange", "grape"}
7
8 // With index and value
9 for index, fruit := range fruits {
10 fmt.Printf("Position %d: %s
11", index, fruit)
12 }
13
14 // Value only (ignore index with _)
15 for _, fruit := range fruits {
16 fmt.Println("Fruit:", fruit)
17 }
18
19 // Index only
20 for i := range fruits {
21 fmt.Println("Index:", i)
22 }
23}
Break, continue, and labels
Go offers break and continue to control flow within loops, just like other languages. But it also supports labels to control nested loops.
1package main
2
3import "fmt"
4
5func main() {
6 // continue: skip to next iteration
7 for i := 1; i <= 10; i++ {
8 if i%2 == 0 {
9 continue // Skip even numbers
10 }
11 fmt.Println("Odd:", i)
12 }
13
14 fmt.Println("---")
15
16 // break: exit the loop
17 for i := 1; i <= 100; i++ {
18 if i > 5 {
19 break
20 }
21 fmt.Println("Number:", i)
22 }
23}
Labels are useful when you have nested loops and want to break out of the outer loop from inside the inner one:
1package main
2
3import "fmt"
4
5func main() {
6outer:
7 for i := 0; i < 5; i++ {
8 for j := 0; j < 5; j++ {
9 if i+j == 4 {
10 fmt.Printf("Exiting at i=%d, j=%d
11", i, j)
12 break outer // Breaks out of the outer loop
13 }
14 fmt.Printf("i=%d, j=%d
15", i, j)
16 }
17 }
18 fmt.Println("End of program")
19}
for statement they refer to. They are mainly used to break out of nested loops, something that in other languages requires auxiliary control variables or separate functions.
Practical examples
Let us combine everything we learned in three classic programming examples.
FizzBuzz: print numbers from 1 to 30. If the number is divisible by 3, print "Fizz". If divisible by 5, print "Buzz". If divisible by both, print "FizzBuzz".
1package main
2
3import "fmt"
4
5func main() {
6 for i := 1; i <= 30; i++ {
7 switch {
8 case i%15 == 0:
9 fmt.Println("FizzBuzz")
10 case i%3 == 0:
11 fmt.Println("Fizz")
12 case i%5 == 0:
13 fmt.Println("Buzz")
14 default:
15 fmt.Println(i)
16 }
17 }
18}
Sum of elements in a slice:
1package main
2
3import "fmt"
4
5func main() {
6 numbers := []int{10, 25, 33, 47, 52, 68, 71, 89}
7 sum := 0
8 largest := numbers[0]
9
10 for _, n := range numbers {
11 sum += n
12 if n > largest {
13 largest = n
14 }
15 }
16
17 average := float64(sum) / float64(len(numbers))
18 fmt.Printf("Sum: %d
19", sum)
20 fmt.Printf("Average: %.2f
21", average)
22 fmt.Printf("Largest: %d
23", largest)
24}
Number guessing game:
1package main
2
3import (
4 "fmt"
5 "math/rand"
6 "time"
7)
8
9func main() {
10 rand.Seed(time.Now().UnixNano())
11 secret := rand.Intn(100) + 1
12 attempts := 0
13
14 fmt.Println("=== Guess the Number ===")
15 fmt.Println("I picked a number between 1 and 100.")
16
17 for {
18 var guess int
19 fmt.Print("Your guess: ")
20 _, err := fmt.Scan(&guess)
21 if err != nil {
22 fmt.Println("Please enter a valid number.")
23 continue
24 }
25
26 attempts++
27
28 switch {
29 case guess < secret:
30 fmt.Println("Too low. Try again.")
31 case guess > secret:
32 fmt.Println("Too high. Try again.")
33 default:
34 fmt.Printf("Correct! The number was %d
35", secret)
36 fmt.Printf("You got it in %d attempts.
37", attempts)
38
39 switch {
40 case attempts <= 5:
41 fmt.Println("Amazing, you are a genius!")
42 case attempts <= 10:
43 fmt.Println("Well done!")
44 default:
45 fmt.Println("You got it, but you can do better.")
46 }
47 return
48 }
49 }
50}
for loop, conditionless switch, continue for input error handling, and return to end the function when the user guesses correctly. It is a great exercise to practice everything covered in this article.
Summary and next article
In this article you learned the tools to control execution flow in Go:
- if/else with init statement: make decisions with narrowly scoped variables
- Comparison and logical operators: build complex conditions with
==,!=,&&,|| - switch: elegant multi-way selection, with or without expression, and optional
fallthrough - Classic for: the only loop in Go, with initialization, condition, and increment
- While-style for: condition only, to repeat while something is true
- Infinite for: exit with
breakorreturn - For range: iterate over slices, arrays, maps, and strings idiomatically
- break, continue, and labels: control execution in simple and nested loops
In the next article (Part 4) we will learn about functions and error handling: how to organize your code into reusable functions, take advantage of multiple return values in Go, and master the if err != nil pattern.
Comments
Sign in to leave a comment
No comments yet. Be the first!