Control Flow
If / Else
Section titled “If / Else”Go’s if statements do not require parentheses but do require braces. The condition can include an Initialization statement.
if x > 0 { fmt.Println("positive")} else if x < 0 { fmt.Println("negative")} else { fmt.Println("zero")}Initialization Statement
Section titled “Initialization Statement”A common pattern is to initialize a variable in the if scope:
if err := doSomething(); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return}// err is not accessible here -- scoped to the if blockThis keeps the variable scoped to where it is needed and avoids polluting the enclosing function Scope.
Idiomatic Error Check
Section titled “Idiomatic Error Check”The initialization statement pattern is the idiomatic way to check errors:
if val, err := compute(); err != nil { log.Fatal(err)} else { fmt.Println(val)}The else is on the same line as the closing brace of the if block. This is enforced by gofmt.
Switch
Section titled “Switch”Go’s switch is more flexible than C’s. Cases break automatically — no fallthrough unless Explicitly requested.
switch os := runtime.GOOS; os {case "darwin": fmt.Println("macOS")case "linux": fmt.Println("Linux")default: fmt.Println("Other")}Switch with No Condition
Section titled “Switch with No Condition”A switch with no condition is equivalent to switch true. This is an idiomatic replacement for Long if-else chains:
switch {case x < 0: fmt.Println("negative")case x == 0: fmt.Println("zero")case x > 0: fmt.Println("positive")}Type Switch
Section titled “Type Switch”A type switch compares types rather than values. It is used with interface values:
func inspect(i interface{}) { switch v := i.(type) { case int: fmt.Printf("integer: %d\n", v) case string: fmt.Printf("string: %s\n", v) case bool: fmt.Printf("boolean: %t\n", v) default: fmt.Printf("unknown type: %T\n", v) }}The variable v has the type of the matched case, not the interface type.
Fallthrough
Section titled “Fallthrough”Use fallthrough to execute the next case unconditionally. It is rarely needed:
switch n := 5; n {case 5: fmt.Println("five") fallthroughcase 4: fmt.Println("four or five")}// Output:// five// four or fiveFor Loops
Section titled “For Loops”Go has exactly one looping construct: for. There is no whileNo do-whileNo loop.
Classic For
Section titled “Classic For”for i := 0; i < 10; i++ { fmt.Println(i)}While-style For
Section titled “While-style For”Omitting all three components creates an infinite loop:
for { // runs forever}Omitting only the init and post creates a while loop:
n := 0for n < 10 { n++}For-range
Section titled “For-range”The range clause iterates over slices, arrays, maps, strings, and channels.
Range over Slice/Array
Section titled “Range over Slice/Array”nums := []int{10, 20, 30}for i, v := range nums { fmt.Printf("index %d: value %d\n", i, v)}Use _ to discard the index (common when only values are needed):
for _, v := range nums { fmt.Println(v)}Range over Map
Section titled “Range over Map”Iteration order over maps is not guaranteed and varies between runs:
m := map[string]int{"a": 1, "b": 2, "c": 3}for k, v := range m { fmt.Printf("%s: %d\n", k, v)}Range over String
Section titled “Range over String”Iterates over Unicode code points (runes), not bytes:
s := "hello"for i, r := range s { fmt.Printf("byte offset %d: rune %c\n", i, r)}Range over Channel
Section titled “Range over Channel”Receives values from a channel until it is closed:
ch := make(chan int, 3)ch <- 1ch <- 2ch <- 3close(ch)
for v := range ch { fmt.Println(v) // 1, 2, 3}Range Semantics
Section titled “Range Semantics”range copies the value for each iteration. For slices and arrays of pointers or structs, this Means you get a copy of the element, not a reference:
items := []struct{ X int }{{1}, {2}, {3}}for _, item := range items { item.X *= 2 // modifies a copy, not items[i]}fmt.Println(items) // [{1} {2} {3}] -- unchangedTo modify elements, use the index:
for i := range items { items[i].X *= 2}fmt.Println(items) // [{2} {4} {6}]For pointers, the copy is still a pointer but dereferences the same underlying data:
items := []*int{new(int), new(int), new(int)}*items[0] = 1for _, p := range items { *p *= 2 // modifies the pointed-to value}Break and Continue
Section titled “Break and Continue”break exits the innermost for``switchOr select. continue skips to the next iteration of The innermost for loop.
Labeled Break
Section titled “Labeled Break”Break out of an outer loop using a label:
outer:for i := 0; i < 5; i++ { for j := 0; j < 5; j++ { if i*j == 6 { break outer } }}fmt.Println("broke at i*j == 6")Labeled Continue
Section titled “Labeled Continue”Skip to the next iteration of an outer loop:
outer:for i := 0; i < 3; i++ { for j := 0; j < 3; j++ { if j == 1 { continue outer } fmt.Printf("%d %d\n", i, j) }}// Output:// 0 0// 1 0// 2 0Go supports goto with restrictions. The label must be defined in the same function, and goto Cannot jump over variable declarations or into inner blocks.
func process(items []int) { for _, item := range items { if item < 0 { goto invalid } fmt.Println(item) } return
invalid: fmt.Println("found negative item")}goto is rarely used in idiomatic Go. Prefer structured control flow (for``ifFunctions).
defer schedules a function call to run when the surrounding function returns. Arguments are Evaluated immediately, but the function call is deferred.
func readConfig(path string) ([]byte, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close()
return io.ReadAll(f)}Deferred Call Order
Section titled “Deferred Call Order”Deferred functions execute in LIFO (last-in, first-out) order:
func main() { defer fmt.Println("first") defer fmt.Println("second") defer fmt.Println("third") // Output: // third // second // first}Defer and Named Return Values
Section titled “Defer and Named Return Values”A deferred function can read and modify named return values:
func double(x int) (result int) { defer func() { result *= 2 }() result = x return // returns x * 2}fmt.Println(double(5)) // 10This works because return first assigns the value to the named return variable, then deferred Functions execute.
Intuition
Section titled “Intuition”Control flow is a choose-your-own-adventure book: Every if, switch, and for is a decision point where the program picks a path. Go’s design — mandatory braces, auto-breaking switch cases, defer running on the way out — is like giving the reader guardrails so they never accidentally skip a page or re-read one twice.
Why it matters: The defer statement is like a sticky note you leave on your desk: “when you leave the office, do this before you go.” It guarantees cleanup happens exactly once, in reverse order, no matter how the function exits — even on panic.
The key insight: Go’s control flow is deliberately simple (one loop keyword, no fallthrough by default) so the program’s path is always obvious to the reader.
Common Pitfalls
Section titled “Common Pitfalls”Forgetting that
switchcases break automatically. Unlike C, Go does not fall through by default. This is a feature, not a bug, but surprises C programmers.Modifying loop variable captures in closures. The loop variable is reused across iterations. Capturing it in a closure captures the same variable:
for _, v := range values {go func() {fmt.Println(v) // all goroutines print the last value}()}
Fix: pass as an argument go func(val int) { ... }(v).
Using
deferin a loop. Eachdeferin a loop accumulates until the function returns. For large loops, this can exhaust memory. Use an immediately-invoked function or call the cleanup directly.Range over
nilmap/slice is safe.for range nildoes nothing — it does not panic. This is by design.Range copies values. Modifying the range variable does not modify the original element. Use index-based access for mutations.
Summary
Section titled “Summary”This topic covers the core concepts of control flow, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.