10. `switch`: cleaner branching
When you'd otherwise write a long if / else if / else if chain, switch
usually reads better. Go's switch is more flexible than most — and it fixes
the single most notorious bug in C-style switches.
The basic form
package main
import "fmt"
func main() {
day := "Sat"
switch day {
case "Sat", "Sun":
fmt.Println("Weekend")
case "Mon":
fmt.Println("Ugh, Monday")
default:
fmt.Println("Weekday")
}
}
switch day compares day against each case. A case can list several
values separated by commas ("Sat", "Sun"). default runs if nothing
matched — it's optional, and can go anywhere, but reads best at the bottom.
No fall-through by default
In C and Java, forgetting break at the end of a case makes execution "fall
through" into the next one — the cause of countless bugs. Go does the safe
thing automatically: each case stops on its own. No break needed.
If you want fall-through, you ask for it explicitly with the fallthrough
keyword — a rare thing you'll almost never need:
package main
import "fmt"
func main() {
switch n := 2; n {
case 2:
fmt.Println("two")
fallthrough // deliberately continue into the next case
case 1:
fmt.Println("one")
}
}
(Note the switch n := 2; n — like if, a switch can declare a variable
scoped to itself before the value it tests.)
Switch with no value: a tidy if-chain
Leave off the value after switch and each case becomes a full boolean
condition. This is the idiomatic replacement for a long if/else if:
package main
import "fmt"
func main() {
score := 82
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("F")
}
}
Cases are checked top to bottom, and the first match wins — that's why
score >= 90 comes before score >= 80. Read it as "the first line that's
true."
Switching on a type
There's a special form that branches on the type of a value — the
type switch. It only makes sense once we have interfaces (module 6), so
here's just a taste to recognize later. %v prints the value; the switch
picks a branch based on what type it actually holds:
package main
import "fmt"
func describe(x any) {
switch v := x.(type) {
case int:
fmt.Printf("an int: %d\n", v)
case string:
fmt.Printf("a string of length %d\n", len(v))
case bool:
fmt.Printf("a bool: %t\n", v)
default:
fmt.Printf("something else: %v\n", v)
}
}
func main() {
describe(42)
describe("hello")
describe(true)
describe(3.14)
}
Don't worry about any or x.(type) yet — just note that switch can
dispatch on type. We'll build up to it.
Your turn
Given month := 4, print the season using a switch. Months 12, 1, 2 →
Winter; 3, 4, 5 → Spring; 6, 7, 8 → Summer; 9, 10, 11 → Autumn.
For month 4 the output is exactly:
Spring
package main
import "fmt"
func main() {
month := 4
// switch on month and print the season
}
package main
import "fmt"
func main() {
month := 4
switch month {
case 12, 1, 2:
fmt.Println("Winter")
case 3, 4, 5:
fmt.Println("Spring")
case 6, 7, 8:
fmt.Println("Summer")
case 9, 10, 11:
fmt.Println("Autumn")
}
}
Next: defer — scheduling cleanup that always runs.