Control Flow Basics
10 examples to get you started with Control Flow - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Create a module:
mkdir flow && cd flow && go mod init example.com/flow. - Save each snippet as
main.goand run withgo run ..
Basic Examples
1. if / else
Branch on a boolean condition.
package main
import "fmt"
func main() {
n := 7
if n%2 == 0 {
fmt.Println("even")
} else {
fmt.Println("odd")
}
}ifdoes not need parentheses around the condition.- Braces are required - Go enforces block structure at compile time.
- The condition must be a boolean expression, not a truthy value.
Related: if with Initialization & switch Patterns - init clauses and switch forms
2. C-style for loop
The classic three-part header: init, condition, post.
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 5; i++ {
sum += i
}
fmt.Println(sum) // 10
}- Go has no separate
whilekeyword - this is the only loop keyword. - The init variable
iis scoped to theforstatement. - Omit init and post for other
forshapes shown in later examples.
Related: for Loop Variants & range Semantics - all
forforms
3. for range over a slice
Iterate index and value together.
package main
import "fmt"
func main() {
langs := []string{"Go", "Rust", "Zig"}
for i, name := range langs {
fmt.Println(i, name)
}
}rangeworks on slices, arrays, maps, channels, and strings.- Use
_to ignore index or value:for _, name := range langs. - Modifying elements through the range value variable does not update the slice (use index).
Related: for Loop Variants & range Semantics - per-collection semantics
4. Expression switch
Match a value against multiple cases.
package main
import "fmt"
func main() {
day := "Mon"
switch day {
case "Sat", "Sun":
fmt.Println("weekend")
default:
fmt.Println("weekday")
}
}- Cases do not fall through automatically (unlike C).
- Multiple values can share one case with commas.
defaultruns when no case matches.
Related: if with Initialization & switch Patterns - tagless and type switches
5. if with init statement
Bind a temporary variable scoped to the if block.
package main
import (
"fmt"
"strconv"
)
func main() {
if n, err := strconv.Atoi("42"); err != nil {
fmt.Println("parse error:", err)
} else {
fmt.Println("parsed:", n)
}
}- The init statement runs once before the condition.
- Variables from init are visible only in the
ifandelseblocks. - This pattern is the idiomatic error check in Go.
Related: Go Control Flow: Expression-Oriented Design - why init clauses matter
6. defer for cleanup
Schedule a call to run when the function returns.
package main
import "fmt"
func main() {
defer fmt.Println("cleanup")
fmt.Println("work")
}- Deferred calls run in last-in-first-out order.
deferexecutes on function exit, including panic unwind.- Pair
defer f.Close()immediately after a successfulOpen().
Related: defer, panic & recover - defer stack and recovery
7. break and continue
Skip an iteration or exit the loop early.
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
if i > 5 {
break
}
fmt.Println(i)
}
}continuejumps to the next iteration.breakexits the innermostfor,switch, orselect.- Labels extend
break/continueto outer loops (see intermediate example).
Related: break, continue & Labeled Control Flow - labeled exits
Intermediate Examples
8. Type switch on interface{}
Branch on dynamic type inside an interface value.
package main
import "fmt"
func describe(v any) {
switch x := v.(type) {
case int:
fmt.Println("int", x)
case string:
fmt.Println("string", x)
default:
fmt.Printf("other %T\n", x)
}
}
func main() {
describe(42)
describe("hi")
}v.(type)is only allowed in aswitchstatement.- The short variable
xholds the typed value in each case. - Prefer typed functions over
anywhen the set of types is known.
Related: if with Initialization & switch Patterns - type switch patterns
9. Labeled break from nested loops
Exit an outer loop without a helper flag.
package main
import "fmt"
func main() {
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i*j == 4 {
break outer
}
fmt.Println(i, j)
}
}
}- The label must be on a
for,switch, orselectstatement. continue outerskips to the next iteration of the labeled loop.- Extract a function when labels make the flow hard to follow.
Related: break, continue & Labeled Control Flow - when labels help
10. panic and recover
Recover stops panic unwinding inside a deferred function.
package main
import "fmt"
func mayPanic() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
panic("boom")
}
func main() {
mayPanic()
fmt.Println("main continues")
}recoverreturns nil unless called from a deferred function during panic.- Normal errors should return
error, not panic. - HTTP servers use this pattern in middleware to avoid crashing the process.
Related: Control Flow in HTTP Middleware - panic-safe handlers
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).