Conditional Flow
Decisions! Go's conditional toolkit is small: if,
else, and switch. There are no parentheses
around conditions, no ternary operator, and braces are mandatory. The
small surface area is intentional: there are fewer ways to write the
same thing.
Learning objectives
- Write
if,if-else, and chainedelse ifstatements. - Use the short-statement form to scope a variable to its
if. - Choose between
switchand anif-elsechain. - Recognize the tagless switch idiom and the
fallthroughkeyword. - Spot a type switch and know what it's for.
if / else
package main
import "fmt"
func main() {
age := 18
if age >= 18 {
fmt.Println("adult")
} else if age >= 13 {
fmt.Println("teen")
} else {
fmt.Println("child")
}
}
Things to notice:
- No parentheses around the condition.
- Braces required, even for one-line bodies.
- The opening
{must be on the same line as theif/else. - The condition must be a
bool: no truthy/falsy.
Short statement form
Go lets you put an initialization statement before the condition,
separated by a semicolon. The initialized variable is scoped to the
if/else blocks:
if n := compute(); n > 0 {
fmt.Println("positive:", n)
} else {
fmt.Println("non-positive:", n)
}
// n is NOT in scope here
This is incredibly common in idiomatic Go for handling errors:
if err := saveUser(u); err != nil {
return fmt.Errorf("save: %w", err)
}
// err is gone, it lived only inside the if
Tightly scoping the error variable avoids "leaking" it into the rest of the function and keeps your code easy to follow.
switch
For matching one value against many candidates, switch
is cleaner than an if-else if ladder:
switch day {
case "Mon", "Tue", "Wed", "Thu", "Fri":
fmt.Println("weekday")
case "Sat", "Sun":
fmt.Println("weekend")
default:
fmt.Println("unknown day")
}
Two important differences from C/Java:
- No implicit fallthrough. Each case ends after its body.
- Multiple values per case are comma-separated, no need for stacked
caselabels.
Cases can be expressions, too, they don't have to be constants:
switch n := rand.Intn(100); {
case n < 50:
fmt.Println("small")
case n < 90:
fmt.Println("medium")
default:
fmt.Println("large")
}
Tagless switch
Drop the value and you get a tagless switch: a much cleaner
replacement for long if-else if chains:
switch {
case temp < 0:
fmt.Println("freezing")
case temp < 10:
fmt.Println("cold")
case temp < 25:
fmt.Println("comfortable")
default:
fmt.Println("hot")
}
The tagless switch is one of those small Go niceties that, once you
use it, you start wishing other languages had. It's the same as
switch true.
fallthrough (rarely needed)
If you really want C-style fall-through into the next case, use
fallthrough:
switch n {
case 1:
fmt.Println("one")
fallthrough
case 2:
fmt.Println("two") // also runs when n == 1
}
Use it sparingly. Most cases where you'd reach for it are clearer written with multiple comma-separated case values.
Type switch (preview)
A type switch matches on the dynamic type of an interface value, not on its value. We'll meet interfaces properly in Chapter 21, but here's the syntax for recognition:
func describe(v any) {
switch x := v.(type) {
case int:
fmt.Printf("int %d\n", x)
case string:
fmt.Printf("string %q (len %d)\n", x, len(x))
case []byte:
fmt.Printf("byte slice (len %d)\n", len(x))
case nil:
fmt.Println("nil")
default:
fmt.Printf("unknown type %T\n", x)
}
}
func main() {
describe(42)
describe("hello")
describe([]byte{1, 2, 3})
describe(nil)
}
The v.(type) syntax only works inside a switch. Inside
each case, x already has the matched type, no further
assertions needed.
Style notes
- Prefer early returns over deep nesting. Instead of
if ok { ... } else { return err }, writeif !ok { return err }first and let the happy path stay flat. - Don't compare booleans to
true/false. Writeif isReady, notif isReady == true. - Use a switch when you have more than two branches on the same value, easier to read than nested if-else.
Check your understanding
Practice exercises
Grade calculator
Write a function grade(score int) string that returns
"A" for 90+, "B" for 80–89, "C" for 70–79, "D" for 60–69, and "F"
otherwise. Use a tagless switch.
Show one possible solution
package main
import "fmt"
func grade(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 70:
return "C"
case score >= 60:
return "D"
default:
return "F"
}
}
func main() {
fmt.Println(grade(95)) // A
fmt.Println(grade(75)) // C
fmt.Println(grade(40)) // F
}
Type-switch on `any`
Write a function describe(v any) string that returns a
short string describing the value: "int N",
"float F", "string S (len L)",
"bool B", or "unknown".
Show one possible solution
package main
import "fmt"
func describe(v any) string {
switch x := v.(type) {
case int:
return fmt.Sprintf("int %d", x)
case float64:
return fmt.Sprintf("float %g", x)
case string:
return fmt.Sprintf("string %q (len %d)", x, len(x))
case bool:
return fmt.Sprintf("bool %t", x)
default:
return "unknown"
}
}
func main() {
for _, v := range []any{1, 1.5, "hi", true, []int{1, 2}} {
fmt.Println(describe(v))
}
}
Further reading
Decisions made.