Control Flow (if, switch)

Reviewed & published by Brayan K

Programs that can't make decisions are just calculators. In this lesson you'll teach your Go code to branch: clean if/else chains without parentheses, the powerful if-with-a-short-statement pattern you'll use constantly for error handling, and Go's tidy switch that needs no break.

Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ if / else if / else

Go's if drops the parentheses other C-style languages require, but the curly braces are mandatory — even for a one-line body. The opening brace must sit on the same line as the condition (Go's formatter enforces this). Conditions are plain boolean expressions you combine with &&, ||, and !.

package main

import "fmt"

func main() {
    score := 82

    // Go's if needs NO parentheses around the condition,
    // but the { braces } are ALWAYS required.
    if score >= 90 {
        fmt.Println("Grade: A")
    } else if score >= 80 {
        fmt.Println("Grade: B")   // this one runs
    } else if score >= 70 {
        fmt.Println("Grade: C")
    } else {
        fmt.Println("Grade: F")
    }

    // Conditions combine with && (and), || (or), ! (not).
    age := 20
    hasTicket := true
    if age >= 18 && hasTicket {
        fmt.Println("Welcome in!")
    }
}

2️⃣ if With a Short Statement

This is one of the most idiomatic patterns in all of Go. You can run a short statement before the condition, separated by a semicolon: if v, err := doThing(); err != nil {...}. The variables it declares are scoped to the if/else only, so they don't leak into the rest of your function. You'll see this everywhere error handling and map lookups appear.

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Go lets you run a short statement BEFORE the condition,
    // separated by a semicolon. The variable it declares is
    // only visible inside the if/else — keeping scope tidy.

    if n, err := strconv.Atoi("42"); err == nil {
        fmt.Println("parsed:", n+1) // parsed: 43
    } else {
        fmt.Println("not a number:", err)
    }

    // 'n' and 'err' do NOT exist out here — they were scoped to the if.

    // This pattern is everywhere in Go: do the work, then check the error,
    // all without leaking variables into the surrounding function.
    if v, ok := lookup("go"); ok {
        fmt.Println("found:", v)    // found: Gopher
    }
}

func lookup(key string) (string, bool) {
    table := map[string]string{"go": "Gopher"}
    v, ok := table[key]
    return v, ok
}

3️⃣ switch

Go's switch is friendlier than in C or Java. Cases do not fall through by default, so you never write break. One case can match several values (case "Sat", "Sun":). And a tagless switch (no value after the keyword) evaluates each case as a boolean — a much cleaner way to write a long if/else if ladder.

package main

import "fmt"

func main() {
    day := "Sat"

    // Go's switch needs NO "break" — cases do not fall through by default.
    switch day {
    case "Sat", "Sun":             // one case can list several values
        fmt.Println("Weekend!")
    case "Mon":
        fmt.Println("Back to work")
    default:
        fmt.Println("A weekday")
    }

    // A "tagless" switch is a clean replacement for a long if/else chain.
    temp := 31
    switch {
    case temp >= 30:
        fmt.Println("Hot")          // this runs
    case temp >= 20:
        fmt.Println("Warm")
    default:
        fmt.Println("Cool")
    }
}

Your turn — two short fixes. First, complete the purchase check.

package main

import "fmt"

func main() {
    // 🎯 YOUR TURN — replace each ___ then run it.
    balance := 50
    price := 30

    // 1) Only buy if the balance is at least the price
    if balance ___ price {        // 👉 >=
        balance = balance - price
        fmt.Println("Bought! Balance:", balance)
    } ___ {                       // 👉 else
        fmt.Println("Not enough funds")
    }

    // ✅ Expected output:  Bought! Balance: 20
}

Now finish the traffic-light switch.

package main

import "fmt"

func main() {
    // 🎯 YOUR TURN — finish the switch then run it.
    light := "yellow"

    switch ___ {                  // 👉 light  (the value to test)
    case "green":
        fmt.Println("Go")
    case "yellow":
        fmt.Println("Slow down")  // this should run
    case "red":
        fmt.Println("Stop")
    }

    // ✅ Expected output:  Slow down
}

🧩 Reorder Challenge

These lines form a tagless switch on a temperature, but they're scrambled. Put them in order so that when temp = 35 it prints Hot.

    default:
    case temp >= 30:
        fmt.Println("Hot")
    switch {
        fmt.Println("Mild")
    }

Why: the switch { header opens the block. Cases are evaluated top to bottom, so case temp >= 30: with its body comes first, then default: with its body, and finally the closing brace. The first case that's true runs and the switch stops — no break needed.

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

FormSyntax
Basic ifif x > 0 { ... }
if / elseif a { ... } else { ... }
if + short stmtif v, err := f(); err != nil { ... }
Value switchswitch x { case 1: ... }
Multi-value casecase "a", "b":
Tagless switchswitch { case x > 0: ... }

🧠 Quick Recall

Predict the output before you reveal the answer.

x := 7; if x > 5 { fmt.Print("big") } else { fmt.Print("small") }

big — 7 is greater than 5, so the first branch runs and the else is skipped.

switch 2 { case 1, 2: fmt.Print("low"); default: fmt.Print("hi") }

low — the value 2 matches the grouped case 1, 2, and Go runs only that case (no fall-through).

n := 9; switch { case n%2 == 0: fmt.Print("even"); default: fmt.Print("odd") }

odd — 9 % 2 is 1, not 0, so the first case is false and default runs.

Frequently Asked Questions

Q: Does Go have a ternary ?: operator?

No — deliberately. Go favours an explicit if/else for clarity. For a simple value choice, a short if with an assignment in each branch reads just fine.

Q: When should I use a tagless switch over if/else?

When you have three or more conditions. A tagless switch lines the conditions up vertically and reads like a table, which is easier to scan than a deep if/else chain.

Q: Why doesn't my variable from the if exist later?

Variables declared in the if's short statement are scoped to the if/else block. That's intentional — it stops temporaries leaking. Declare it outside the if if you need it afterwards.

Mini-Challenge: One-Number FizzBuzz

Decide which message to print for a single number using the modulo operator %. Check the both-divisible case first. Write it yourself and match the example output.

package main

import "fmt"

func main() {
    // 🎯 MINI-CHALLENGE: FizzBuzz for a single number
    // Given n := 15, print:
    //   "FizzBuzz" if n is divisible by BOTH 3 and 5
    //   "Fizz"     if divisible by 3 only
    //   "Buzz"     if divisible by 5 only
    //   the number itself otherwise
    // Use the % (modulo) operator: a%b == 0 means b divides a.
    //
    // ✅ Example output (for n = 15):  FizzBuzz

    n := 15
    // your code here
}

🎉 Lesson Complete!

Practice quiz

Does Go's if statement require parentheses around the condition?

  • Yes, always
  • Only with else
  • No — parentheses are not used
  • Only for booleans

Answer: No — parentheses are not used. Go drops the parentheses other C-style languages require, but the curly braces are always mandatory.

Are the curly braces required for a one-line if body in Go?

  • Yes — braces are always required
  • No, they are optional
  • Only in functions
  • Only for else if

Answer: Yes — braces are always required. Braces are mandatory in Go even for a single-statement body, and the opening brace must be on the same line.

In the if score short statement form 'if n, err := f(); err == nil', where is n visible?

  • The whole function
  • The package
  • Nowhere
  • Only inside the if/else block

Answer: Only inside the if/else block. Variables declared in an if's short statement are scoped to the if/else block, keeping them from leaking out.

Do Go switch cases fall through to the next case by default?

  • Yes
  • No — each case stops on its own
  • Only numeric cases
  • Only with default

Answer: No — each case stops on its own. Go cases do not fall through by default, so you never write break; use the fallthrough keyword if you truly need it.

What does switch 2 { case 1, 2: print("low"); default: print("hi") } print?

  • low
  • hi
  • lowhi
  • nothing

Answer: low. The value 2 matches the grouped case 1, 2, and Go runs only that case (no fall-through), printing low.

What is a 'tagless' switch (switch with no value after the keyword)?

  • A syntax error
  • It matches on type
  • Each case is evaluated as a boolean — like an if/else ladder
  • It always runs default

Answer: Each case is evaluated as a boolean — like an if/else ladder. A tagless switch evaluates each case as a boolean condition, a clean replacement for a long if/else if chain.

Can one case match several values?

  • No
  • Yes — case "Sat", "Sun":
  • Only with ||
  • Only integers

Answer: Yes — case "Sat", "Sun":. A single case can list several comma-separated values, e.g. case "Sat", "Sun":.

Which operators combine boolean conditions in Go?

  • and, or, not
  • &, |, ~
  • AND, OR, NOT
  • &&, ||, !

Answer: &&, ||, !. Go uses && (and), || (or), and ! (not) to combine and negate boolean conditions.

Given n := 9; switch { case n%2 == 0: print("even"); default: print("odd") } — what prints?

  • even
  • odd
  • both
  • nothing

Answer: odd. 9 % 2 is 1, not 0, so the first case is false and default runs, printing odd.

Does Go have a ternary ?: operator?

  • Yes
  • Only for ints
  • No — use an explicit if/else
  • Only in switch

Answer: No — use an explicit if/else. Go deliberately has no ternary operator; it favours an explicit if/else for clarity.

Continue this course