In the previous tutorial, you learned about functions and error handling. Now it is time to learn how to control the flow of your program with if, switch, and for.
Go keeps control flow simple. There is only one loop keyword: for. No while, no do-while. Just for. The switch statement is also simpler and more powerful than in most languages.
if / else
The basic if statement works like most languages. But Go does not need parentheses around the condition:
package main
import "fmt"
func main() {
age := 20
if age >= 18 {
fmt.Println("You are an adult")
} else {
fmt.Println("You are a minor")
}
}
Output:
You are an adult
You can chain multiple conditions with else if:
package main
import "fmt"
func classifyTemperature(temp int) string {
if temp < 0 {
return "Freezing"
} else if temp < 10 {
return "Cold"
} else if temp < 20 {
return "Cool"
} else if temp < 30 {
return "Warm"
} else {
return "Hot"
}
}
func main() {
temps := []int{-5, 5, 15, 25, 35}
for _, t := range temps {
fmt.Printf("%d°C is %s\n", t, classifyTemperature(t))
}
}
Output:
-5°C is Freezing
5°C is Cold
15°C is Cool
25°C is Warm
35°C is Hot
if with Short Statement
This is one of Go’s best features. You can put a short statement before the condition. The variable you declare is only available inside the if block:
package main
import (
"fmt"
"strconv"
)
func main() {
// The short statement declares err, which is only available in this if block
if num, err := strconv.Atoi("42"); err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Number:", num)
}
// num and err are NOT available here
// This is the most common pattern — error checking
if err := doSomething(); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Success")
}
func doSomething() error {
return nil
}
Output:
Number: 42
Success
The short statement pattern keeps error-handling code compact. You will see it everywhere in Go code:
if err := os.Remove("file.txt"); err != nil {
fmt.Println("Could not delete file:", err)
}
switch
Go’s switch statement is cleaner than in most languages. There is no fall-through by default, so you don’t need break statements:
package main
import "fmt"
func dayType(day string) string {
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
return "Weekday"
case "Saturday", "Sunday":
return "Weekend"
default:
return "Unknown"
}
}
func main() {
fmt.Println(dayType("Monday")) // Weekday
fmt.Println(dayType("Saturday")) // Weekend
fmt.Println(dayType("Holiday")) // Unknown
}
Notice: multiple values in one case are separated by commas. No need for multiple case statements.
switch Without a Condition
You can use switch without a condition. It acts like a chain of if-else statements, but it is easier to read:
package main
import "fmt"
func classifyScore(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 70:
return "C"
case score >= 60:
return "D"
default:
return "F"
}
}
func main() {
scores := []int{95, 85, 72, 63, 45}
for _, s := range scores {
fmt.Printf("Score %d: Grade %s\n", s, classifyScore(s))
}
}
Output:
Score 95: Grade A
Score 85: Grade B
Score 72: Grade C
Score 63: Grade D
Score 45: Grade F
This is a very common pattern in Go. When you have many conditions to check, a conditionless switch is cleaner than a chain of if-else.
switch with Short Statement
Just like if, switch can have a short statement:
package main
import (
"fmt"
"time"
)
func main() {
switch hour := time.Now().Hour(); {
case hour < 12:
fmt.Println("Good morning!")
case hour < 17:
fmt.Println("Good afternoon!")
default:
fmt.Println("Good evening!")
}
}
Type Switch
A type switch checks the type of a value. This is useful when working with interfaces:
package main
import "fmt"
func describe(value interface{}) string {
switch v := value.(type) {
case int:
return fmt.Sprintf("Integer: %d", v)
case float64:
return fmt.Sprintf("Float: %.2f", v)
case string:
return fmt.Sprintf("String: %q (length %d)", v, len(v))
case bool:
return fmt.Sprintf("Boolean: %t", v)
default:
return fmt.Sprintf("Unknown type: %T", v)
}
}
func main() {
fmt.Println(describe(42))
fmt.Println(describe(3.14))
fmt.Println(describe("hello"))
fmt.Println(describe(true))
}
Output:
Integer: 42
Float: 3.14
String: "hello" (length 5)
Boolean: true
The value.(type) syntax only works inside a switch statement. The variable v gets the correct type in each case branch.
fallthrough
By default, Go’s switch does not fall through to the next case. If you want fall-through behavior (like C or Java), use the fallthrough keyword:
package main
import "fmt"
func main() {
num := 1
switch num {
case 1:
fmt.Println("One")
fallthrough
case 2:
fmt.Println("Two")
fallthrough
case 3:
fmt.Println("Three")
}
}
Output:
One
Two
Three
In practice, you rarely need fallthrough. Most Go code does not use it.
for — The Only Loop
Go has only one loop keyword: for. But it can do everything that for, while, and do-while do in other languages.
Classic for Loop
package main
import "fmt"
func main() {
// Classic: init; condition; post
for i := 0; i < 5; i++ {
fmt.Printf("i = %d\n", i)
}
}
Output:
i = 0
i = 1
i = 2
i = 3
i = 4
for as while
Drop the init and post statements. Now for acts like while:
package main
import "fmt"
func main() {
count := 1
for count <= 5 {
fmt.Printf("count = %d\n", count)
count++
}
}
Output:
count = 1
count = 2
count = 3
count = 4
count = 5
Infinite Loop
Drop everything. Just for:
package main
import "fmt"
func main() {
sum := 0
for {
sum++
if sum >= 10 {
break // Exit the loop
}
}
fmt.Println("Sum:", sum)
}
Output:
Sum: 10
Use break to exit an infinite loop. This pattern is common for programs that run forever (like servers) or when the exit condition is complex.
for range — Iterating Over Collections
for range is the way to iterate over slices, maps, strings, and channels in Go:
package main
import "fmt"
func main() {
// Range over a slice
fruits := []string{"apple", "banana", "cherry"}
for index, fruit := range fruits {
fmt.Printf("%d: %s\n", index, fruit)
}
fmt.Println()
// Range over a string (gives you runes)
for i, ch := range "Hello" {
fmt.Printf("%d: %c\n", i, ch)
}
fmt.Println()
// Range over a map
ages := map[string]int{
"Alex": 25,
"Sam": 30,
"Jordan": 22,
}
for name, age := range ages {
fmt.Printf("%s is %d years old\n", name, age)
}
}
Output:
0: apple
1: banana
2: cherry
0: H
1: e
2: l
3: l
4: o
Alex is 25 years old
Sam is 30 years old
Jordan is 22 years old
If you only need the index:
for i := range fruits {
fmt.Println(i)
}
If you only need the value:
for _, fruit := range fruits {
fmt.Println(fruit)
}
for range with integers (Go 1.22+)
Since Go 1.22, you can use range with an integer:
package main
import "fmt"
func main() {
// Range over an integer — same as for i := 0; i < 5; i++
for i := range 5 {
fmt.Printf("i = %d\n", i)
}
}
Output:
i = 0
i = 1
i = 2
i = 3
i = 4
This is a clean way to loop a specific number of times.
break and continue
break exits the loop. continue skips to the next iteration:
package main
import "fmt"
func main() {
// continue — skip even numbers
fmt.Println("Odd numbers:")
for i := 1; i <= 10; i++ {
if i%2 == 0 {
continue
}
fmt.Printf("%d ", i)
}
fmt.Println()
// break — stop when we find the target
fmt.Println("\nSearching for 7:")
numbers := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 7, 8}
for i, n := range numbers {
if n == 7 {
fmt.Printf("Found 7 at index %d\n", i)
break
}
}
}
Output:
Odd numbers:
1 3 5 7 9
Searching for 7:
Found 7 at index 10
Labels
Labels let you break out of nested loops:
package main
import "fmt"
func main() {
// Find the first pair that sums to 10
outer:
for i := 1; i <= 9; i++ {
for j := 1; j <= 9; j++ {
if i+j == 10 {
fmt.Printf("Found: %d + %d = 10\n", i, j)
break outer // Break out of BOTH loops
}
}
}
fmt.Println("Done")
}
Output:
Found: 1 + 9 = 10
Done
Without the label, break would only exit the inner loop. With break outer, it exits both loops.
A Complete Example
Here is a program that uses all the control flow concepts from this tutorial:
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println("=== GO-5: Control Flow ===")
fmt.Println()
// FizzBuzz — a classic programming exercise
fmt.Println("FizzBuzz (1-20):")
for i := 1; i <= 20; i++ {
switch {
case i%15 == 0:
fmt.Printf("%2d: FizzBuzz\n", i)
case i%3 == 0:
fmt.Printf("%2d: Fizz\n", i)
case i%5 == 0:
fmt.Printf("%2d: Buzz\n", i)
default:
fmt.Printf("%2d: %d\n", i, i)
}
}
fmt.Println()
// Count words in a sentence
sentence := "Go is a simple and powerful language"
words := strings.Fields(sentence)
wordCount := make(map[string]int)
for _, word := range words {
lower := strings.ToLower(word)
wordCount[lower]++
}
fmt.Println("Word counts:")
for word, count := range wordCount {
fmt.Printf(" %s: %d\n", word, count)
}
fmt.Println()
// Find prime numbers using a labeled loop
fmt.Println("Prime numbers (2-30):")
for num := 2; num <= 30; num++ {
isPrime := true
for div := 2; div*div <= num; div++ {
if num%div == 0 {
isPrime = false
break
}
}
if isPrime {
fmt.Printf("%d ", num)
}
}
fmt.Println()
fmt.Println()
// Type switch example
values := []interface{}{42, "hello", 3.14, true, nil}
fmt.Println("Type descriptions:")
for _, v := range values {
switch v := v.(type) {
case int:
fmt.Printf(" int: %d\n", v)
case string:
fmt.Printf(" string: %q\n", v)
case float64:
fmt.Printf(" float64: %.2f\n", v)
case bool:
fmt.Printf(" bool: %t\n", v)
case nil:
fmt.Println(" nil value")
}
}
fmt.Println()
// Simple number guessing game logic
secret := 42
guesses := []int{25, 50, 42, 30}
fmt.Printf("Guessing game (secret: %d):\n", secret)
for attempt, guess := range guesses {
fmt.Printf(" Attempt %d: guessed %d — ", attempt+1, guess)
switch {
case guess < secret:
fmt.Println("Too low!")
case guess > secret:
fmt.Println("Too high!")
default:
fmt.Println("Correct!")
}
if guess == secret {
break
}
}
}
Output:
=== GO-5: Control Flow ===
FizzBuzz (1-20):
1: 1
2: 2
3: Fizz
4: 4
5: Buzz
6: Fizz
7: 7
8: 8
9: Fizz
10: Buzz
11: 11
12: Fizz
13: 13
14: 14
15: FizzBuzz
16: 16
17: 17
18: Fizz
19: 19
20: Buzz
Word counts:
go: 1
is: 1
a: 1
simple: 1
and: 1
powerful: 1
language: 1
Prime numbers (2-30):
2 3 5 7 11 13 17 19 23 29
Type descriptions:
int: 42
string: "hello"
float64: 3.14
bool: true
nil value
Guessing game (secret: 42):
Attempt 1: guessed 25 — Too low!
Attempt 2: guessed 50 — Too high!
Attempt 3: guessed 42 — Correct!
Common Mistakes
1. No parentheses around conditions.
// Wrong — Go does not use parentheses
if (x > 0) {
// ...
}
// Correct
if x > 0 {
// ...
}
Go does not need (or allow) parentheses around if, for, or switch conditions.
2. Opening brace must be on the same line.
// Wrong — Go requires { on the same line
if x > 0
{
// ...
}
// Correct
if x > 0 {
// ...
}
This is enforced by Go’s formatter. The { must always be on the same line as the if, for, switch, or func keyword.
3. Forgetting that switch cases don’t fall through.
// In Go, this only prints "One" — no fall-through by default
switch num {
case 1:
fmt.Println("One")
case 2:
fmt.Println("Two")
}
If you come from C or Java, remember that Go’s switch stops after the first matching case. Use fallthrough if you need the old behavior (but you rarely do).
Source Code
You can find the complete source code for this tutorial on GitHub:
Related Articles
- Go Tutorial #4: Functions and Error Handling — Functions and the error pattern
What’s Next?
In the next tutorial, Go Tutorial #6: Arrays, Slices, and Maps, you will learn:
- Arrays — fixed-size collections
- Slices — dynamic arrays (the most used data structure in Go)
- Maps — key-value pairs
make,append,len,caprangeover slices and maps
This is part 5 of the Go Tutorial series. Follow along to learn Go from scratch.