Code of the Day
BeginnerCore syntax

Control flow

if/else with init statements, Go's single for loop in three forms, and switch without fallthrough.

GoBeginner11 min read
Recommended first
By the end of this lesson you will be able to:
  • Write if/else statements including the init statement form
  • Use for as a traditional C-style loop, a while-style loop, and an infinite loop
  • Iterate over slices and maps with range
  • Write a switch statement and understand that Go cases do not fall through by default

The Fundamentals track covers control flow as the skeleton of any program: decisions, repetition, and early exits. Go's take is deliberately minimal. There is no while keyword, no do/while, no repeat/until. Everything that involves repetition uses the single keyword for. This might sound limiting, but it means you have fewer things to remember — and once you see the three forms, you'll have everything you need.

if / else

Go's if statement looks familiar:

score := 85

if score >= 90 {
    fmt.Println("A")
} else if score >= 80 {
    fmt.Println("B")
} else {
    fmt.Println("C or below")
}

Two things differ from C or Java:

  1. No parentheses around the condition. The Go formatter (go fmt) removes them if you write them.
  2. Braces are required — you cannot write a single-line if without braces.

The init statement

Go's if has an optional init statement separated from the condition by a semicolon. The init statement runs first, and any variable it declares is scoped to the if/else block:

if err := doSomething(); err != nil {
    fmt.Println("failed:", err)
    return
}
// err is not in scope here

This pattern appears constantly in Go — call a function, capture the error, and handle it, all in one line. The variable err disappears after the block ends, which keeps the namespace clean.

Scoped variables reduce noise. When you write if v, err := f(); err != nil, neither v nor err pollutes the surrounding scope. Compare this to languages where you must declare them before the if and they remain visible for the rest of the function. Go's init statement is small, but it keeps code tidier.

for — Go's only loop

Go has one loop keyword: for. It handles every looping pattern.

Form 1: traditional (init; condition; post)

for i := 0; i < 5; i++ {
    fmt.Println(i)   // 0 1 2 3 4
}

Identical to a C for loop. The init statement, condition, and post statement are all optional.

Form 2: condition only (while equivalent)

Omit the init and post to get a while loop:

n := 1
for n < 100 {
    n *= 2
}
fmt.Println(n)   // 128

Form 3: infinite loop

Omit the condition entirely to loop forever. Use break to exit:

for {
    input := readInput()
    if input == "quit" {
        break
    }
    process(input)
}

continue skips the rest of the loop body and moves to the next iteration. Both break and continue work the same in all three forms.

Go has no while keyword on purpose. The language designers decided one loop primitive is enough. When you see for with a condition and no init/post, read it as a while loop. It becomes natural very quickly.

range — iterating over collections

The most common loop in real Go programs uses to iterate over a slice, string, or map:

fruits := []string{"apple", "banana", "cherry"}
for i, fruit := range fruits {
    fmt.Printf("%d: %s\n", i, fruit)
}

range returns two values: the index and the element. If you only need the element, discard the index with _:

for _, fruit := range fruits {
    fmt.Println(fruit)
}

If you only need the index (for example, to modify elements in place), just omit the second variable:

for i := range fruits {
    fruits[i] = strings.ToUpper(fruits[i])
}

Ranging over a map gives you key-value pairs (in random order — Go map iteration is deliberately non-deterministic):

ages := map[string]int{"Alice": 30, "Bob": 25}
for name, age := range ages {
    fmt.Printf("%s is %d\n", name, age)
}

switch

switch dispatches on a value and matches the first case that equals it:

day := "Tuesday"
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
    fmt.Println("Weekday")
case "Saturday", "Sunday":
    fmt.Println("Weekend")
default:
    fmt.Println("Unknown day")
}

Key differences from C/Java switch:

  • No fallthrough by default. Each case ends automatically — no break needed. If you genuinely want fallthrough to the next case, write fallthrough explicitly (rare).
  • Multiple values per case separated by commas.
  • No condition needed — a switch with no expression acts like a chain of if/else if:
x := 42
switch {
case x < 0:
    fmt.Println("negative")
case x == 0:
    fmt.Println("zero")
default:
    fmt.Println("positive")
}

switch without expression is idiomatic for range checks. When you have three or more conditions checking the same variable with different operators, a conditionless switch is often cleaner than a chain of if/else if. The handles both identically.

Check your understanding

Knowledge check

  1. 1.
    How do you write a while loop in Go?
  2. 2.
    In Go, what happens when a switch case matches and executes its body?
  3. 3.
    When you range over a map in Go, in what order are the key-value pairs visited?

Do it yourself

Write a program that:

  1. Uses a for loop with range to iterate over []int{3, 7, 2, 9, 1, 5}.
  2. Tracks the running maximum.
  3. Prints the maximum after the loop.

Then rewrite the same logic using a traditional for i := 0; ... loop and confirm you get the same answer.

go run main.go
go fmt ./...
go vet ./...

Where to go next

You can make decisions, loop in all the ways you need, and dispatch on values cleanly. Next: arrays, slices, and maps — Go's built-in collection types and the operations that make them useful.

Finished reading? Mark it complete to track your progress.

On this page