HowtoGo
Home / Control Flow / Switch Statements
Control Flow

Switch Statements

Terms Switch

A switch statement allows you to execute different code blocks based on the value of a variable or expression. It is a cleaner alternative to multiple if-else statements.

The switch keyword is followed by the variable or expression to evaluate. Each case represents a possible value, and the default case handles any unmatched values.

switch day {
    case "Monday":
        // do something for Monday
    case "Tuesday":
        // do something for Tuesday
    default:
        // do something for other days
}

You can group multiple values in a single case by separating them with commas. This allows you to handle multiple cases with the same code block.

switch num {
    case 1, 2, 3:
        // do something for 1, 2, or 3
    case 4:
        // do something for 4
    default:
        // do something for other numbers
}

The output reflects the matched case based on the value of the variable. In this example, if day is "Monday", it will print "It's Monday!".

Terminal
$ go run switch.go
It's Monday!

Switching on a type

A type switch reads the concrete type stored inside an interface value. The type keyword goes where a value normally would, and t holds the value at the matched type in every branch.

case nil matches an interface holding nothing. default catches every type the cases skipped.

func describe(v any) string {
    switch t := v.(type) {
    case string:
        return fmt.Sprintf("string %q, %d chars", t, len(t))
    case int:
        return fmt.Sprintf("int %d, doubled is %d", t, t*2)
    case bool:
        return fmt.Sprintf("bool %t, negated is %t", t, !t)
    case nil:
        return "no value at all"
    default:
        return fmt.Sprintf("unhandled type %T", t)
    }
}

A case can list several types. t then keeps the interface type any instead of a concrete one, since the branch runs for more than one.

Interface types work as cases too. error and fmt.Stringer match any value implementing them, and the first matching case wins.

func label(v any) string {
    switch t := v.(type) {
    case int, int64, float64:
        // Three types in one case, so t is still an any here.
        return fmt.Sprintf("number %v", t)
    case error:
        return "error: " + t.Error()
    case fmt.Stringer:
        return "stringer: " + t.String()
    default:
        return fmt.Sprintf("unhandled %T", t)
    }
}

time.Second is a time.Duration, whose underlying type is int64. The case int64 branch skips it, because a type switch matches the named type rather than what it is built on.

func main() {
    for _, v := range []any{"howtogo", 21, true, nil, 3.5} {
        fmt.Println(describe(v))
    }
    fmt.Println(label(int64(42)))
    fmt.Println(label(errors.New("disk full")))
    fmt.Println(label(time.Second))
}

Running both functions over a mix of values prints one line per call.

Terminal
$ go run main.go
string "howtogo", 7 chars
int 21, doubled is 42
bool true, negated is false
no value at all
unhandled type float64
number 42
error: disk full
stringer: 1s