Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 7

    If, Else & Switch

    Go's control flow is explicit and readable.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Go's control flow is explicit and readable. if statements support initialization statements (if err := fn(); err != nil), eliminating temp variables. switch auto-breaks (no fallthrough unless specified) and supports expression-less switches for clean multi-branch logic.

    Go 1.22 enhanced switch to accept any comparable type and improved for-loop variable scoping. Pattern-style switches on type are done via type switches — a distinct construct covered with interfaces later.

    Mastering guard clauses (early return on error), switch on enums, and avoiding deep nesting is essential for readable handlers, middleware, and service methods interviewers will review in take-home assignments.

    The story

    An API gateway health checker classifies upstream responses before routing traffic: 2xx is healthy, 429 triggers backoff, 5xx marks the origin degraded. SRE dashboards at companies like Stripe use similar branching logic — guard clauses reject malformed status codes before severity rules run.

    switch on HTTP status ranges keeps the routing table readable compared to deeply nested if chains that become unmaintainable across dozens of microservices.

    Understanding the topic

    Key concepts

    • if init; condition { } — init statement scoped to if/else block.
    • else if chains handle multiple conditions; else is optional.
    • switch expression { case val: } — no break needed; fallthrough is explicit.
    • switch without expression equals switch true — cleaner than if-else chains.
    • case supports comma-separated values: case 1, 2, 3:
    • Type switch: switch v := x.(type) { case int: } for interface values.
    text
    flowchart TD
    Start([Condition]) --> Check{if true?}
    Check -->|yes| TrueBlock[Execute]
    Check -->|no| FalseBlock[Else]
    TrueBlock --> Merge[Continue]
    FalseBlock --> Merge

    Step-by-step explanation

    1. Evaluate if condition; execute first matching block.
    2. Init statement in if runs once; variables scoped to if/else.
    3. switch evaluates cases top-to-bottom; first match executes.
    4. default case runs when no case matches.
    5. fallthrough continues to next case — rarely used, document why.
    6. Type switch binds variable to concrete type in each case.

    Practical code example

    HTTP-style routing logic with if-init error pattern and switch on status:

    go
    package main
    import "fmt"
    type Status string
    const (
    StatusActive Status = "active"
    StatusPending Status = "pending"
    StatusDisabled Status = "disabled"
    )
    func describeAccount(status Status, balance float64) string {
    switch status {
    case StatusActive:
    if balance <= 0 {
    return "active but zero balance"
    }
    return fmt.Sprintf("active with balance %.2f", balance)
    case StatusPending:
    return "awaiting verification"
    case StatusDisabled:
    return "account disabled"
    default:
    return "unknown status"
    }
    }
    func main() {
    fmt.Println(describeAccount(StatusActive, 150.75))
    fmt.Println(describeAccount(StatusPending, 0))
    }

    Line-by-line code explanation

    • if code < 100 || code > 599 is a guard clause rejecting invalid HTTP status codes.
    • return HealthUnknown exits early before main classification logic runs.
    • switch { case code >= 200 && code < 300: uses a tagless switch for range matching.
    • return HealthOK handles the healthy arm without extra nesting.
    • case code == 429: matches rate-limit responses for special retry handling.
    • case code >= 500: catches all server errors in a single arm.
    • default: provides safe fallback behavior for unexpected codes.
    • switch tier := classify(code); tier { ... } assigns a switch result to a variable in one expression.

    Key takeaway: if init; err != nil is the idiomatic error check. switch on typed constants (iota enums) is cleaner than long if-else chains.

    Real-world use

    Where you'll use this in production

    • HTTP middleware branching on method, path, and headers.
    • Validation pipelines with early return on first failure.
    • State machine transitions in order processing services.
    • Error classification switching on error types or codes.

    Best practices

    • Use guard clauses — return early instead of deep nesting.
    • Prefer switch over long if-else chains for discrete values.
    • Always include default case in switch for exhaustiveness awareness.
    • Use if err := ...; err != nil immediately after calls returning error.
    • Avoid fallthrough unless documenting C-style intentional fall-through.
    • Extract complex conditions into well-named boolean variables.

    Common mistakes

    • Using switch with fallthrough accidentally — Go breaks by default unlike C.
    • Comparing floats with == in conditions — use epsilon or integer cents.
    • Deep nesting beyond 3 levels — refactor to functions or early return.
    • Forgetting braces on if — Go requires them always.
    • Type switch without ok check on type assertion elsewhere.

    Advanced interview questions

    Q1Beginnerif init statement purpose?
    Declares variable scoped to if block — common pattern: if err := fn(); err != nil { return err }.
    Q2BeginnerDoes Go switch fall through?
    No — each case breaks automatically; use fallthrough keyword explicitly to continue.
    Q3IntermediateSwitch without expression?
    Equivalent to switch true — each case is a boolean expression; replaces if-else chains.
    Q4IntermediateRefactor nested ifs in legacy handler?
    Extract functions, use guard clauses, switch on enum, or strategy map[Status]func().
    Q5AdvancedModel complex insurance rules maintainably in Go.
    Rule interface with Evaluate(ctx) bool; compose slice of rules; table-driven tests per rule; config-driven thresholds.

    Summary

    if init; cond scopes temporary variables cleanly for error checks. switch auto-breaks; use for enums and multi-value cases. Guard clauses and early return beat deep nesting. Type switches handle interface{} or any values safely. Next lesson: for loops — Go's only looping construct.

    Ready to mark this lesson complete?Track your journey across the entire course.