Switch Statements
Learn how to use switch statements in Go for multi-way branching, covering expression switches, condition-less switches, fallthrough, break, and common pitfalls
A switch statement selects one block of code to run from a set of possibilities. It does the same job as a long chain of if–else if conditions but is clearer when a single expression or condition gets compared against many different values. Go’s switch is deliberately simpler than its counterparts in C, C++, or Java — it removes a few sharp edges and adds flexibility that makes common branching patterns more concise.
Automatic break:
Go executes only the matching case, then exits the switch. You do not need a break to stop execution from falling through into the next case. This is the opposite of C‑family languages, where missing break causes unintended fallthrough.
Basic Expression Switch
An expression switch compares a value against several case values and runs the first one that matches.
package main
import "fmt"
func main() {
fruit := "apple"
switch fruit {
case "banana":
fmt.Println("It's a banana.")
case "apple":
fmt.Println("It's an apple.")
case "orange":
fmt.Println("It's an orange.")
default:
fmt.Println("Unknown fruit.")
}
}
The switch evaluates fruit. The first case "banana" does not match, so Go moves on. The second case "apple" matches, prints It's an apple., and then the switch ends — no break is written and none is needed. The default case runs only if nothing matches.
The order matters: cases are checked top‑to‑bottom, and only the first match executes. Overlapping case values are allowed syntactically (except when they are constants that duplicate), but only the first one to match will ever run.
Condition‑less Switch (Switch True)
When you omit the expression entirely, Go treats the switch as switch true. Each case must be a boolean condition. The first condition that evaluates to true runs.
num := 75
switch {
case num < 0:
fmt.Println("Negative")
case num >= 0 && num < 50:
fmt.Println("Low")
case num >= 50 && num < 100:
fmt.Println("Medium")
default:
fmt.Println("High")
}
This replaces a series of if–else if checks with a structure that is visually easier to scan. The example prints Medium because num >= 50 && num < 100 is the first true condition.
You might think of this form as “I have a set of independent logical tests; run the first one that passes.” The conditions do not need to reference a single shared variable — each case can check completely different things.
Multiple Values per Case
You can list several values in a single case, separated by commas. The case matches if the switch expression equals any one of them.
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("Weekend")
default:
fmt.Println("Weekday")
}
This is equivalent to writing separate cases that all share the same body, but avoids repetition. A comma‑separated list is not a range — it is an exact enumeration. If you have ten values, you list all ten. The switch expression and the listed values must be of comparable types.
Clean grouping:
Using commas for related values keeps the switch table compact. When you see case 1, 2, 3: in real code, it’s almost always grouping logically related outcomes.
The fallthrough Keyword
Although Go’s default behavior is to stop after a matching case, you can force execution into the very next case body by placing fallthrough as the last statement inside a case block.
char := 'a'
switch char {
case 'a':
fmt.Println("Letter a")
fallthrough
case 'b':
fmt.Println("Also triggers for a")
case 'c':
fmt.Println("Only for c")
}
Output:
Letter a
Also triggers for a
Here, case 'a' matches. After printing Letter a, fallthrough hands control to the body of case 'b' — and critically, the condition of case 'b' is not re‑checked. The second fmt.Println runs unconditionally. After that, the switch terminates as normal.
fallthrough only advances to the immediately next case, not multiple cases. You cannot place it anywhere except as the final statement in a case clause — even a comment after it is illegal. It also cannot be used inside a type switch.
Use fallthrough sparingly:
fallthrough surprises readers because it contradicts Go’s normal switch model. It exists mainly to support specific porting scenarios from C. If your logic requires multiple branches to execute sequentially, consider extracting shared code into a function instead.
break Inside a Switch
Because Go automatically breaks after each case, you will rarely write break inside a switch. However, a break statement is legal and stops the switch immediately — useful when you want to exit early from a case body before it reaches its natural end.
A more common real-world use involves labeled break to exit an enclosing loop from deep inside a switch:
outer:
for _, v := range []int{1, 2, 3, 4, 5} {
switch {
case v == 2:
fmt.Println("Skipping 2")
break // exits the switch, not the loop
case v == 4:
fmt.Println("Aborting at 4")
break outer // exits the for loop entirely
default:
fmt.Printf("Processing %d\n", v)
}
}
Output:
Processing 1
Skipping 2
Processing 3
Aborting at 4
An unlabeled break inside the switch only breaks out of the switch. The labeled break outer breaks out of the named for loop, demonstrating how break interacts with nested control flow — something that comes up in parsers, state machines, and any loop that uses a switch to handle discrete states.
Switch with an Initialization Statement
Like if, a switch can include a short statement that runs before the expression is evaluated. The variables declared there are scoped to the entire switch block.
package main
import (
"fmt"
"runtime"
)
func main() {
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("Running on macOS.")
case "linux":
fmt.Println("Running on Linux.")
default:
fmt.Printf("Running on %s.\n", os)
}
}
The variable os exists only inside the switch — it is inaccessible afterwards. This scoping keeps temporary variables close to where they are used and prevents them from leaking into the surrounding function.
Case Order and Overlapping Conditions
Cases are evaluated from top to bottom, and the first match wins. This becomes especially important in condition‑less switches where multiple conditions can be true at the same time.
score := 87
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("F")
}
score is 87, so both score >= 80 and score >= 70 are true. Only "B" is printed because that case appears first. If you reorder the cases so that score >= 70 comes first, you’d get "C" instead, which would be a bug. This top‑down evaluation is the same behaviour as if–else if chains.
Common Mistakes and Pitfalls
Don’t expect fallthrough from other languages:
If you’re used to C, Java, or JavaScript, you might assume cases fall through by default. In Go they don’t. Forgetting this won’t cause a compile error, but it will produce logic bugs when a case that you thought would cascade simply stops.
fallthrough can only go one case deep:
fallthrough transfers to the body of the very next case. There is no way to skip over cases or continue into a second subsequent case. If you need multi‑step fallthrough, you are probably modelling the problem incorrectly for Go.
Case values must be comparable and of compatible types:
The switch expression and all case values must be valid in a Go == comparison. Comparing a string against an integer is a compile‑time error. Comparing a slice against another slice (even if identical) will panic at runtime because slices are not comparable. Stick to concrete types like numbers, strings, and booleans, or ensure the expression is an interface holding a comparable underlying value.
Duplicate constant cases cause compile errors:
If you write case 1: ... case 1: with literal constants, the compiler will reject it as a duplicate. However, non‑constant variables that happen to have the same value are not checked at compile time; they follow normal top‑down matching.
Fallthrough is illegal in type switches:
A type switch (switch v := x.(type)) does not permit fallthrough. If you need to handle multiple types with shared logic, list them in a single case separated by commas, or call a shared function from each case.
When to Use Switch vs If-Else
Choose a switch when you are comparing one value or one set of conditions against multiple distinct alternatives. The visual structure of a switch makes the branches equally prominent, which helps a reader scan the possibilities quickly.
Choose an if–else chain when the number of branches is very small (two or three) or when the conditions are complex and not easily expressed as a flat list. A single if with a few else if branches is perfectly idiomatic — there is no obligation to reach for a switch.
The condition‑less switch (switch true) blurs the line. It is essentially an if–else if chain with different syntax. The deciding factor is readability: if the conditions are numerous and short, a switch true can look cleaner than a long if–else if ladder.
Summary
Go’s switch is a branching construct stripped of the accidental complexity that C‑derived languages carried. It auto‑breaks, accepts any comparable values, and supports a no‑expression form that works like a readable cascade of boolean conditions. The existence of fallthrough and labeled break covers the rare edge cases where the default behaviour is not enough, but both are signals to step back and consider whether the control flow is really as simple as it could be.