10. `switch`: cleaner branching

📖 Reading · 7 min
💡 Every code box below is live — edit it and hit Run.

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.