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
- Write if / else if / else without parentheses
- Combine conditions with &&, || and !
- Use the if-with-short-statement scoping pattern
- Replace long if-chains with a tagless switch
- Group multiple values in one case
- Understand why Go's switch needs no break
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")
}- case temp >= 30:
- fmt.Println("Hot")
- 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
- 💡 Prefer the early return. Check the error first and return, so your happy-path code stays un-indented and easy to read.
- 💡 A tagless switch beats a long if-chain for readability whenever you have three or more branches.
- 💡 Use fallthrough only when you mean it. It's an explicit keyword in Go precisely because falling through is rarely what you want.
- 💡 Scope variables to the if. The if x := f(); x > 0 form keeps temporaries out of the wider function.
Common Errors (and the fix)
- "syntax error: unexpected {" — you wrapped the condition in parentheses out of habit, e.g. if (x > 0) {. Go doesn't want them: write if x > 0 {.
- "missing condition in if statement" or a brace on the next line — Go requires the { on the same line as the condition. The formatter gofmt enforces this.
- Expecting cases to fall through — they don't in Go. If you genuinely need it, add the explicit fallthrough keyword as the last statement in the case.
- "x declared and not used" after an if x := f(); ... — if you never read x in either branch, Go complains. Use it or drop it.
📋 Quick Reference
| Form | Syntax |
|---|---|
| Basic if | if x > 0 { ... } |
| if / else | if a { ... } else { ... } |
| if + short stmt | if v, err := f(); err != nil { ... } |
| Value switch | switch x { case 1: ... } |
| Multi-value case | case "a", "b": |
| Tagless switch | switch { 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!
- ✅ if needs no parentheses but always needs braces
- ✅ Combine conditions with &&, ||, !
- ✅ The if v, err := f(); err != nil pattern scopes variables to the block
- ✅ switch needs no break and a case can list several values
- ✅ A tagless switch cleanly replaces a long if/else chain
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
- Previous: Data Types & Constants (iota)
- Next: for Loops & range — Go's single loop keyword and ranging over collections