Conditions and Switch
Go has one conditional statement and one switch, and both do more than their equivalents elsewhere.
if score > 90 {
fmt.Println("excellent")
} else if score > 70 {
fmt.Println("good")
} else {
fmt.Println("keep going")
}No parentheses around the condition, and the braces are mandatory even for one line. The opening brace must be on the same line — Go's automatic semicolon insertion breaks the statement otherwise, which is why gofmt will not let you move it.
The statement-scoped if#
The idiom you will write most often in Go:
if err := doSomething(); err != nil {
return fmt.Errorf("do something: %w", err)
}
// err does not exist hereThe initialiser runs first, then the condition. Anything it declares is scoped to the if and its else branches — which keeps short-lived variables like err out of the enclosing function.
if user, err := findUser(id); err != nil {
return err
} else {
fmt.Println(user.Name) // both user and err visible here
}In practice you rarely need the else — returning early is the dominant Go style:
user, err := findUser(id)
if err != nil {
return err
}
fmt.Println(user.Name) // the happy path stays unindentedswitch#
Go's switch has no automatic fallthrough, so no break is needed:
switch status {
case "pending":
fmt.Println("waiting")
case "shipped", "delivered": // several values in one case
fmt.Println("on its way")
default:
fmt.Println("unknown")
}switch with no condition#
A switch with nothing after it means switch true, which replaces a long if/else chain:
switch {
case score > 90:
grade = "A"
case score > 70:
grade = "B"
default:
grade = "C"
}This is idiomatic Go and usually reads better than the equivalent else if ladder.
switch with an initialiser#
switch hour := time.Now().Hour(); {
case hour < 12:
fmt.Println("morning")
case hour < 18:
fmt.Println("afternoon")
default:
fmt.Println("evening")
}fallthrough#
Available, and rare:
switch n {
case 1:
fmt.Println("one")
fallthrough // continue into the next case regardless of its condition
case 2:
fmt.Println("two")
}
// n == 1 prints bothType switches#
A switch on the dynamic type of an interface value. This is how you handle a value that could be several things:
func describe(v any) string {
switch x := v.(type) {
case nil:
return "nothing"
case int:
return fmt.Sprintf("int %d", x*2) // x is an int here
case string:
return "string of length " + strconv.Itoa(len(x))
case []int:
return fmt.Sprintf("%d ints", len(x))
case error:
return "error: " + x.Error()
default:
return fmt.Sprintf("unhandled %T", x)
}
}Inside each case, x has that specific type. %T in the default branch prints the actual type, which is invaluable when debugging.
Comparison and logic#
== != < <= > >=
&& || !&& and || short-circuit. Two Go-specific points:
There is no ternary operator. Write the if, or a small helper:
label := "off"
if enabled {
label = "on"
}There are no truthy values. A condition must be a bool:
if count { } // error: non-boolean condition
if count != 0 { } // fine
if name != "" { } // fineThat strictness removes a whole class of bug found in dynamically typed languages — and it is one reason generated Go is less likely to be subtly wrong than generated JavaScript.
Exercise#
package main
import "fmt"
func main() {
scores := []int{95, 72, 58, 88, 100}
// For each score print "<score>: <grade>" where grade is
// A for 90+, B for 80-89, C for 70-79, F below 70.
// Use a conditionless switch.
fmt.Println(scores)
}Common questions#
Why is there no ternary operator?#
A deliberate omission — the Go authors judged that nested ternaries hurt readability more than the brevity helps. The three-line if is the intended form, and in practice it is rarely the thing that makes Go verbose.
When should I use a type switch?#
When handling a value whose type genuinely varies — parsing arbitrary JSON, walking an AST, or inspecting an error chain. If you find yourself type-switching on your own types regularly, an interface with a method is usually the better design.
Should I use else?#
Sparingly. The dominant Go style is to handle the error case and return early, leaving the happy path unindented. A function whose main logic sits inside an else block is usually one early return away from being clearer.
Get the Go agent pack
A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for Go. One email, then occasional updates when the tooling shifts. No course pitch.
AGENTS.md now — no email needed.