The if statement
An if statement executes a block of code only when a condition is true. The condition is a boolean expression — it must evaluate to either true or false.
age := 20
if age >= 18 {
fmt.Println("adult")
}
The parentheses around the condition are optional in many languages but are absent by convention in Go — the compiler accepts them, but gofmt removes them. The curly braces, however, are always required. Go does not allow single-line if bodies without braces.
Go requires a strict bool
Unlike C, JavaScript, or Python, Go does not treat non-zero integers, non-empty strings, or non-nil pointers as truthy. The condition must be an expression that evaluates to exactly bool. Writing if n { ... } where n is an int is a compile error.
The if-else statement
An else block runs when the condition is false. It gives you a second path of execution — exactly one of the two blocks will run:
score := 72
if score >= 60 {
fmt.Println("pass")
} else {
fmt.Println("fail")
}
The else keyword must appear on the same line as the closing brace of the if block. Putting it on the next line is a compile error because Go's automatic semicolon insertion places a semicolon after the }, making the else unreachable:
// compile error: unexpected else
if score >= 60 {
fmt.Println("pass")
}
else {
fmt.Println("fail")
}
The if-else if statement
When you need to evaluate more than two conditions, you can chain else if clauses. Go evaluates them from top to bottom and executes the first block whose condition is true. If no condition matches and an else is present, that block runs instead:
temp := 22
if temp < 0 {
fmt.Println("freezing")
} else if temp < 15 {
fmt.Println("cold")
} else if temp < 25 {
fmt.Println("comfortable")
} else {
fmt.Println("hot")
}
Once a condition matches, the remaining branches are skipped entirely — Go does not fall through to subsequent else if blocks. The final else is optional; without it, if no condition matches, execution continues after the entire chain.
Long chains are a signal
A long if-else if chain evaluating the same variable against different values is often better expressed as a switch statement. switch is cleaner to read, easier to extend, and makes the intent — "choose one of several cases" — explicit.
If with an init statement
Go allows an optional initialization statement before the condition, separated by a semicolon. The variable declared in the init statement is scoped to the entire if-else block — it exists only within that block and is not accessible after it:
if x := compute(); x > 0 {
fmt.Println("positive:", x)
} else {
fmt.Println("non-positive:", x)
}
// x is not accessible here
This is not just syntactic convenience — it is an intentional scoping mechanism. By declaring the variable inside the if, you signal that its purpose is exclusively tied to that conditional check. It cannot leak into the surrounding scope and affect unrelated code.
The pattern is used constantly for error handling in Go. Functions that can fail return a value and an error. The init statement captures both, and the body handles the failure path:
if err := doSomething(); err != nil {
fmt.Println("error:", err)
return
}
This is the idiomatic Go way to handle errors inline, without declaring a throwaway variable in the outer scope just to check it once. The variable exists for exactly as long as it is needed — no more.
Both branches share the scoped variable
A variable declared in the init statement is visible in all branches — the if body, every else if body, and the else body. This is why x is accessible in the else block in the first example above.