Beginner

Control Flow in Go

Learn conditionals, switch statements, and loops in Go with practical Docker-ready examples covering if/else, switch, for loops, and loop control

Control flow determines the order in which your program’s statements run. It is how a program makes decisions and repeats work. Go takes a deliberately minimal approach here: where most languages offer several looping keywords, Go provides exactly one — for. This is consistent with Go’s design philosophy of being a small, learnable language.

As a procedural, imperative language, Go uses familiar structured control flow: if/else conditionals, switch statements, and for loops. But Go adds its own twists. There are no parentheses around conditions, braces are always required, the switch statement does not fall through by default, and there is deliberately no ternary operator (? :). Go’s authors decided clarity was worth a little extra typing.

In this tutorial you will learn how to branch with if/else, match values with switch, iterate with the versatile for loop, range over collections, and control loops with break and continue — including Go’s labeled loops.

Conditionals: if / else

Go’s if statement needs no parentheses around the condition, but the braces are mandatory. A useful feature is the optional initialization statement: you can declare a variable scoped to the if/else block before the condition.

Create a file named conditionals.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
	temperature := 72

	// Basic if / else if / else chain
	if temperature > 80 {
		fmt.Println("It's hot outside")
	} else if temperature > 60 {
		fmt.Println("The weather is pleasant")
	} else {
		fmt.Println("Bring a jacket")
	}

	// if with an initialization statement.
	// score only exists inside this if/else block.
	if score := 85; score >= 90 {
		fmt.Println("Grade: A")
	} else if score >= 80 {
		fmt.Printf("Grade: B (score %d)\n", score)
	} else {
		fmt.Println("Grade: C or below")
	}
}

The init-statement form (if score := 85; score >= 80) is idiomatic Go. It keeps short-lived variables tightly scoped, which is especially common when checking errors: if err := doSomething(); err != nil { ... }.

Switch Statements

Go’s switch is more powerful and safer than its C counterpart. Cases do not fall through automatically, so you rarely need break. A single case can match multiple values, and a switch with no condition becomes a clean replacement for a long if/else chain.

Create a file named switch.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

func main() {
	day := "Saturday"

	// Expression switch: no break needed, cases don't fall through.
	// A single case can list several values.
	switch day {
	case "Saturday", "Sunday":
		fmt.Println("It's the weekend!")
	case "Friday":
		fmt.Println("Almost the weekend")
	default:
		fmt.Println("A regular workday")
	}

	// Switch with no condition acts like an if/else chain.
	hour := 14
	switch {
	case hour < 12:
		fmt.Println("Good morning")
	case hour < 18:
		fmt.Println("Good afternoon")
	default:
		fmt.Println("Good evening")
	}

	// fallthrough explicitly forces execution into the next case.
	switch n := 1; n {
	case 1:
		fmt.Println("One")
		fallthrough
	case 2:
		fmt.Println("Two")
	case 3:
		fmt.Println("Three")
	}
}

Because Go does not fall through by default, you avoid a whole class of bugs common in C and Java where a forgotten break causes accidental execution of the next case. When you genuinely want fall-through behavior, you opt in with the explicit fallthrough keyword.

Loops: the for Statement

Go has a single looping keyword: for. It covers every looping need through three forms — the classic three-component loop, a condition-only “while” form, and an infinite loop.

Create a file named loops.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func main() {
	// 1. Classic three-component loop: init; condition; post
	for i := 1; i <= 5; i++ {
		fmt.Printf("Count: %d\n", i)
	}

	// 2. Condition-only form (Go's equivalent of a while loop)
	n := 10
	for n > 1 {
		n = n / 2
		fmt.Printf("n is now %d\n", n)
	}

	// 3. Infinite loop with a break to exit
	total := 0
	for {
		total += 10
		if total >= 30 {
			break
		}
	}
	fmt.Printf("Total reached %d\n", total)
}

There is no while keyword in Go — for condition { } does the job. Likewise, for { } with no clauses is an infinite loop, which you exit with break. One keyword, three behaviors.

Ranging Over Collections and Loop Control

The range clause iterates over slices, arrays, maps, strings, and channels. Combined with break and continue — and Go’s labeled loops — it handles most real-world iteration.

Create a file named range_loops.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import "fmt"

func main() {
	// range over a slice yields the index and the value
	fruits := []string{"apple", "banana", "cherry"}
	for index, fruit := range fruits {
		fmt.Printf("%d: %s\n", index, fruit)
	}

	// range over a map yields the key and the value
	ages := map[string]int{"Alice": 30}
	for name, age := range ages {
		fmt.Printf("%s is %d\n", name, age)
	}

	// continue skips to the next iteration
	for i := 1; i <= 6; i++ {
		if i%2 != 0 {
			continue // skip odd numbers
		}
		fmt.Printf("Even: %d\n", i)
	}

	// A label lets break exit an outer loop, not just the inner one
outer:
	for i := 1; i <= 3; i++ {
		for j := 1; j <= 3; j++ {
			if i*j > 4 {
				fmt.Printf("Stopping at %d x %d\n", i, j)
				break outer
			}
		}
	}
}

Note: Go intentionally randomizes map iteration order, so the example above uses a single-key map to keep output predictable. Labeled statements (outer:) work with both break and continue, giving you precise control over nested loops without resorting to flag variables.

Running with Docker

You can run every example without installing Go locally by using the official golang:1.23 image.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the official Go image
docker pull golang:1.23

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app golang:1.23 go run conditionals.go

# Run the switch example
docker run --rm -v $(pwd):/app -w /app golang:1.23 go run switch.go

# Run the loops example
docker run --rm -v $(pwd):/app -w /app golang:1.23 go run loops.go

# Run the range and loop-control example
docker run --rm -v $(pwd):/app -w /app golang:1.23 go run range_loops.go

Expected Output

Running conditionals.go:

The weather is pleasant
Grade: B (score 85)

Running switch.go:

It's the weekend!
Good afternoon
One
Two

Running loops.go:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
n is now 5
n is now 2
n is now 1
Total reached 30

Running range_loops.go:

0: apple
1: banana
2: cherry
Alice is 30
Even: 2
Even: 4
Even: 6
Stopping at 2 x 3

Key Concepts

  • No parentheses, mandatory braces — Conditions are written without surrounding (), but { } is always required, even for a single statement.
  • if with an init statementif x := f(); x > 0 { } scopes a variable to the conditional block; this is the idiomatic way to handle errors in Go.
  • switch does not fall through — Cases break automatically. Use the explicit fallthrough keyword only when you want the next case to run.
  • One loop keywordfor covers C-style loops, while-style loops (for cond {}), and infinite loops (for {}). There is no while or do-while.
  • range for iteration — Iterate over slices, maps, strings, and channels; map order is randomized by design.
  • break and continue with labels — Labeled loops let you break out of or continue an outer loop directly, avoiding flag variables.
  • No ternary operator — Go deliberately omits ? :; use a short if/else instead, favoring readability over brevity.

Running Today

All examples can be run using Docker:

docker pull golang:1.23
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining