if and else
An if statement lets a Go program choose which block of code to run based on whether a condition is true, and else together with else if let you chain alternative branches for when it isn’t. Every program that makes a decision — validating input, branching on an error, picking a response based on a value — relies on this construct, so getting comfortable with Go’s particular flavor of it (mandatory braces, no parentheses, no ternary operator, and an optional init statement) is one of the first steps to writing idiomatic Go.
Overview / How it works
Go’s if statement looks similar to C, Java, or JavaScript at first glance, but it has several deliberate differences that trip up newcomers.
First, the condition must be a genuine boolean expression — a value of type bool. Go has no concept of “truthy” integers, pointers, or strings the way C or Python do. You cannot write if x { ... } when x is an int; the compiler rejects it with a type error. You must write an explicit comparison, such as if x != 0 { ... }. This is a deliberate design choice: it removes an entire class of bugs where a stray assignment or an unintended non-zero value silently makes a branch run.
Second, the condition is never wrapped in parentheses. if (x > 0) { ... } is valid syntax, but gofmt — the standard formatter every Go project runs — strips the parentheses automatically, so idiomatic Go always reads if x > 0 { ... }.
Third, the opening brace must be on the same line as the if, else if, or else keyword. This isn’t just a style preference — it’s required by the language grammar. Go’s lexer automatically inserts a semicolon at the end of certain lines (any line that ends in an identifier, a literal, a closing bracket, or a few other tokens). If you put the opening brace on its own line after if x > 0, the lexer inserts an invisible semicolon after the condition, which turns the block into a syntax error. So braces on the same line aren’t optional style — they’re load-bearing.
Fourth, an if can carry an optional short init statement before the condition, separated by a semicolon: if init; condition { ... }. The init statement runs once, before the condition is evaluated, and anything it declares with := is scoped to the entire if/else if/else chain — visible in every branch, but invisible outside the chain. This is the idiomatic home for the classic Go error-check pattern: if err := doSomething(); err != nil { ... }. It keeps the error variable from leaking into the surrounding function, where it might accidentally get reused or shadowed.
Fifth, Go deliberately has no ternary operator (no condition ? a : b). The language designers left it out on purpose, judging that a compact conditional expression tends to get abused for unreadable one-liners. In Go, a conditional value assignment is always spelled out as an explicit if/else assigning to a variable, or wrapped in a small helper function if you need it repeatedly.
Under the hood, once the compiler has checked that a condition is a bool, it compiles an if/else chain down to ordinary conditional jump instructions — evaluate the condition, jump past the block if false, otherwise fall through and run it, then jump past the remaining branches. There’s no runtime type coercion or boxing involved, because the type checking already happened at compile time; this is one reason Go’s conditionals are as fast as hand-written low-level branching.
Logical operators && (AND), || (OR), and ! (NOT) combine boolean expressions inside a condition, and &&/|| both short-circuit: in a() && b(), if a() returns false, b() is never called, because the overall result is already known to be false. The same applies to || — if the left operand is true, the right operand is skipped. This matters when the right-hand expression has side effects or could panic (like dereferencing a pointer that might be nil): put the safety check first, e.g. if p != nil && p.Value > 0 { ... }.
Syntax
The general forms of an if statement in Go:
// simple form
if condition {
// runs when condition is true
}
// with an alternative branch
if condition {
// runs when condition is true
} else {
// runs when condition is false
}
// chained branches, evaluated top to bottom
if condition1 {
// ...
} else if condition2 {
// ...
} else {
// runs only if none of the above matched
}
// with a short init statement, scoped to the whole chain
if initStatement; condition {
// ...
}
| Part | Meaning |
|---|---|
condition |
A bool expression — no parentheses, no truthy values |
{ } |
Mandatory braces around every branch, opening brace on the same line |
initStatement; |
Optional short statement (often :=) run once before the condition; its variables are scoped to the whole chain |
else if |
Chains another condition, checked only if the previous ones were false |
else |
Optional catch-all branch when no condition matched |
Examples
Example 1: a basic if/else
package main
import "fmt"
func main() {
number := 7
if number%2 == 0 {
fmt.Println("even")
} else {
fmt.Println("odd")
}
}
Output:
odd
The condition number%2 == 0 evaluates the remainder of 7 / 2, which is 1, so the comparison to 0 is false and the else branch runs.
Example 2: chaining with else if
package main
import "fmt"
func main() {
score := 82
if score >= 90 {
fmt.Println("Grade: A")
} else if score >= 80 {
fmt.Println("Grade: B")
} else if score >= 70 {
fmt.Println("Grade: C")
} else {
fmt.Println("Grade: F")
}
}
Output:
Grade: B
Go checks each condition in order. score >= 90 is false, so it moves on; score >= 80 is true, so "Grade: B" prints and the remaining else if/else branches are skipped entirely — only one branch in a chain ever runs.
Example 3: if with an init statement
package main
import (
"fmt"
"strconv"
)
func main() {
input := "42"
if value, err := strconv.Atoi(input); err != nil {
fmt.Println("could not parse:", err)
} else {
fmt.Println("parsed value:", value)
}
}
Output:
parsed value: 42
strconv.Atoi converts a string to an int, returning the value and an error. The init statement value, err := strconv.Atoi(input) runs once, and both value and err are visible in both the if and else branches. Since "42" parses cleanly, err is nil, the condition err != nil is false, and the else branch prints the parsed value.
How it works step by step
Walking through Example 3’s execution:
- The init statement
value, err := strconv.Atoi(input)runs exactly once, before anything else — this happens regardless of which branch ultimately executes. valueanderrcome into existence at this point, scoped to the entireif/elsechain that follows — not to the surroundingmainfunction.- The condition
err != nilis evaluated using theerrjust produced. - Because
errisnil, the condition isfalse, so Go skips theifblock entirely and jumps to theelseblock. - The
elseblock runs, printing the value. Once the chain ends,valueanderrgo out of scope — referencing them after the closing brace would be a compile error.
Common Mistakes
1. Assuming a variable from the init statement survives outside the if/else chain
A variable declared in an if‘s init statement (or inside one of its blocks) only lives inside that chain — not after it:
if x := 10; x > 0 {
fmt.Println("positive:", x)
}
fmt.Println(x) // compile error: undefined: x
The fix is to declare the variable in the enclosing scope first, then assign to it inside the if if you need the value afterward:
package main
import "fmt"
func main() {
var x int
if computed := 10; computed > 0 {
x = computed
fmt.Println("positive:", x)
}
fmt.Println("still visible:", x)
}
Output:
positive: 10
still visible: 10
2. Writing = instead of == in a condition
Coming from a C-family background, it’s easy to type a single = when you mean a comparison. In C this silently compiles (and is a classic bug); in Go it’s a compile-time error, because = is an assignment statement, not a boolean expression:
count := 5
if count = 10 {
fmt.Println("count is ten")
}
// compile error: count = 10 (untyped assignment) cannot be used as a bool value
Go’s grammar and its refusal to treat anything but bool as a condition catch this immediately, but the fix is still worth internalizing — always use == to compare:
package main
import "fmt"
func main() {
count := 5
if count == 10 {
fmt.Println("count is ten")
} else {
fmt.Println("count is not ten:", count)
}
}
Output:
count is not ten: 5
3. Writing an else after a return (or break/continue)
When every branch of an if ends in return, an accompanying else is redundant and adds a needless level of nesting:
func classify(n int) string {
if n < 0 {
return "negative"
} else {
return "non-negative"
}
}
Idiomatic Go drops the else and lets execution simply fall through to the next statement, which reads more like a guard clause:
func classify(n int) string {
if n < 0 {
return "negative"
}
return "non-negative"
}
Both versions behave identically, but Go style (and tools like golint/staticcheck) strongly prefer the second: fewer nested blocks make the common case easier to scan.
Best Practices
- Prefer the
if init; conditionform for values you only need inside the branch — especially theif err := f(); err != nil { ... }pattern — so temporary variables don’t pollute the enclosing scope. - Drop the
elsewhen theifbranch always returns, breaks, or continues; let the rest of the function read as the “normal” path (this is often called using guard clauses). - Keep conditions readable: if a condition needs several
&&/||combined, consider assigning it to a well-namedboolvariable or extracting a small function, e.g.isEligible := age >= 18 && hasConsent. - Order
else ifchains from most specific to most general, the way Example 2 checks the highest grade boundary first. - Put nil/bounds checks first in a combined condition so short-circuit evaluation protects the rest, e.g.
if p != nil && p.Value > 0. - Never silently discard an error to make a condition simpler — check it explicitly, even if that means an extra line.
Practice Exercises
- Write a program that declares an integer variable and prints
"positive","negative", or"zero"depending on its value, using anif/else if/elsechain. - Write a program that loops from 1 to 15 and, for each number, prints
"Fizz"if divisible by 3,"Buzz"if divisible by 5,"FizzBuzz"if divisible by both, and the number itself otherwise. (Hint: check divisibility by both first, since a number divisible by both 3 and 5 is also divisible by each individually.) - Using
strconv.Atoiand theif init; conditionpattern, write a program that tries to parse the string"abc"into an integer and prints an appropriate message for the parse failure. (Expected output starts withcould not parse:.)
Summary
ifconditions must be abool— Go has no truthy values and no parentheses around the condition.- Braces are mandatory, and the opening brace must stay on the same line as
if/else if/elsebecause of automatic semicolon insertion. else ifchains are checked top to bottom; only the first true branch (or the finalelse) runs.- The optional init statement (
if init; condition) scopes its variables to the wholeif/elsechain — ideal for error checks likeif err := f(); err != nil. - Go has no ternary operator by design — use an explicit
if/elseor a small helper function instead. &&and||short-circuit, so ordering nil/safety checks first in a combined condition avoids panics.- Drop unnecessary
elseblocks after areturnto keep code flat and idiomatic.
