Control Flow
if / for / switch / defer — minimal by design.
Go Control Flow
Go's control flow has just three constructs: if, for, and switch. There's no while, no do-while, no ternary. The minimalism is intentional.
if
if score >= 90 {
grade = "A"
} else if score >= 80 {
grade = "B"
} else {
grade = "F"
}
The condition needs no parentheses. The braces are mandatory — there's no single-line if x: do_y() form.
A Go-specific shape: declare a value inside the if, valid only inside the if/else:
if user, err := fetchUser(id); err == nil {
use(user)
} else {
log.Printf("fetch failed: %v", err)
}
// `user` and `err` are out of scope here
This is the canonical pattern for "check the error before using the value".
for is the only loop
Go has one loop keyword. It does the work of for, while, do-while, and forever:
// C-style three-clause: for-loop counterpart to other languages.
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// while-style: just a condition.
for x > 0 {
x--
}
// Infinite — break to exit.
for {
chunk := fetch()
process(chunk)
if chunk.IsLast() {
break
}
}
// Range over a slice/map/string/channel.
for i, x := range xs {
fmt.Println(i, x)
}
// Range over just keys (or just indexes for slices).
for i := range xs {
fmt.Println(i)
}
// Range over just values — use `_` to discard the index.
for _, x := range xs {
fmt.Println(x)
}
Range over a map yields (key, value); range over a string yields (byteIndex, rune); range over a channel yields values until the channel is closed.
break, continue
break exits the innermost loop or switch. continue jumps to the next iteration. Same semantics as every other C-family language.
for _, line := range lines {
if strings.HasPrefix(line, "#") {
continue // skip comments
}
if line == "END" {
break // stop reading
}
process(line)
}
For nested loops you can use a labelled break:
outer:
for _, row := range grid {
for _, cell := range row {
if cell == target {
break outer
}
}
}
switch
Go's switch is friendlier than C/JavaScript:
- Cases don't fall through by default. No need for
breakbetween cases. - Cases can be expressions, not just constants.
- No expression after
switchis shorthand forswitch true.
switch status {
case "ok":
return 200
case "not_found":
return 404
case "server_err", "bad_gateway": // multiple values per case
return 502
default:
return 500
}
// switch as a chain of conditions.
switch {
case score >= 90:
grade = "A"
case score >= 80:
grade = "B"
default:
grade = "F"
}
To get explicit fall-through, use the fallthrough keyword:
switch x {
case 1:
fmt.Println("one")
fallthrough
case 2:
fmt.Println("one or two")
}
defer
defer schedules a function call to run when the surrounding function returns:
func process(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // runs on every return path
// do work...
return nil
}
Multiple defers run in LIFO order. The classic use is paired open/close, lock/unlock, transaction begin/commit. Anything that needs cleanup.
No ternary
If you want a one-line conditional assignment, you write a regular if:
status := "anonymous"
if user.LoggedIn {
status = "active"
}
Or you write a tiny helper. Go's authors chose explicitness over a ?: operator on purpose — code reads more uniformly without it.