---
title: If Statements in Go
description: Understand Go if statements, including basic syntax, the short statement form, else-if chains, error guarding, and common pitfalls
---
Go programs make decisions with `if` statements. An `if` statement evaluates a condition and executes a block of code only when that condition is `true`. Unlike many other languages, Go has a deliberately minimal design: no parentheses around the condition, mandatory braces, and no ternary operator. The result is a syntax that is consistently readable and eliminates entire categories of style debates.
## Writing a Basic If Statement
At its simplest, an `if` statement checks whether a condition holds and runs the indented block when it does.
```go
package main
import "fmt"
func main() {
temperature := 31
if temperature > 30 {
fmt.Println("It's hot outside.")
}
}
The condition temperature > 30 is a boolean expression. If it evaluates to true, the code inside the curly braces executes. If it's false, the entire block is skipped and execution continues after the closing brace.
Braces Are Required:
Even for a single-statement body, the curly braces { } are mandatory. Writing if temperature > 30 fmt.Println("hot") is a compile-time error. This rule eliminates the "dangling else" ambiguity and makes code structure unambiguous.
Go does not require parentheses around the condition. You may occasionally see if (x > 0) in legacy or ported code, but idiomatic Go omits them. The compiler will not complain, but gofmt will not add them, and the community convention is to leave them out.
Adding Else and Else If Branches
When a condition fails, you often need an alternative path. Go provides else and else if exactly as you would expect.
score := 73
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")
}
The conditions are evaluated top to bottom. The first condition that evaluates to true causes its block to execute, and all subsequent branches are ignored. The final else is a catch-all; it runs only if none of the preceding conditions matched.
The else if is not a separate keyword—it is simply an else whose immediate block is a single if statement. Stylistically, gofmt will keep } else if on one line, which reinforces this mental model.
The Short Statement Before the Condition
One of Go's distinctive features is the ability to run a short statement before the condition, separated by a semicolon. Variables declared in that statement are scoped to the if block (including any else if or else branches) and do not leak into the surrounding function.
package main
import (
"fmt"
"math"
)
func rootApprox(x float64) float64 {
if v := math.Sqrt(x); v < 2.0 {
return v
}
return 2.0
}
func main() {
fmt.Println(rootApprox(1.44)) // prints 1.2
fmt.Println(rootApprox(9)) // prints 2.0
}
Here, v is computed once and then compared. It can be used inside the if body, but it does not exist after the closing brace. This pattern keeps related logic close together and prevents the accumulation of short-lived variables in the outer scope.
Scope Hygiene:
When you find yourself declaring a variable on one line only to test it on the next, reach for the short if statement. It signals that the variable exists solely for the branch decision and reduces clutter in the surrounding scope.
You can use the short statement for any expression that produces a value. A classic example is fetching a result from a map and then checking its presence:
if val, ok := cache[key]; ok {
// use val
}
The ok boolean is available only inside the conditional block, which prevents accidental use of the value when the lookup failed.
Nested If Statements
An if block can contain another if statement. While nested conditionals are sometimes necessary, deep nesting often signals that a function is taking on too much. In Go, early returns and extracted helper functions are preferred over multiple levels of indentation.
func processOrder(age int, hasID bool, orderTotal float64) string {
if age >= 18 {
if hasID {
if orderTotal > 0 {
return "Order confirmed"
}
return "Order total must be greater than zero"
}
return "ID required for purchase"
}
return "Must be 18 or older"
}
When you encounter three or more levels of nesting, consider flattening with early returns or moving some logic into a separate function.
Combining Conditions with Logical Operators
Real-world decisions rarely hinge on a single condition. Go's logical operators && (AND), || (OR), and ! (NOT) let you combine boolean expressions to form more precise tests.
a && b– true only if bothaandbare true.a || b– true if at least one ofaorbis true.!a– true ifais false. Go uses short-circuit evaluation: for&&, if the left operand is false the right operand is not evaluated; for||, if the left operand is true the right is skipped. This matters when the right operand has side effects or could panic.
if user.IsAuthenticated && user.HasRole("admin") {
fmt.Println("Access granted")
}
In this example, user.HasRole is never called if the user is not authenticated, avoiding a potentially expensive or panicking call.
Unchecked Nil Dereference:
When combining checks, ensure nil guards appear first. Writing user.HasRole("admin") && user != nil will panic if user is nil because the left side is evaluated before the nil check. Always place nil checks on the left.
The Idiomatic Error-Guard Pattern
Go uses explicit error handling instead of exceptions. The convention is to return an error as the last return value from a function and check it immediately with an if statement. This is so pervasive that it has a name: the error‑guard pattern.
func readConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config file: %w", err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing config: %w", err)
}
return &cfg, nil
}
The pattern is consistent: call a function, capture its return values, and immediately test err != nil. On failure, you either return the error, wrap it with context, or handle it and continue. The short if statement works beautifully here because err is scoped to the guard block, allowing a clean err redeclaration in subsequent blocks.
Error Handling is Not Special Syntax:
The error-guard is not a language feature; it is a community convention enforced by gofmt and linters. The err variable is just a regular variable of type error. The pattern's power comes from its uniformity—every Go developer recognizes it instantly.
Common Mistakes and Misconceptions
No Ternary Operator:
Go does not have a ? : operator. Code like result := x > 0 ? a : b will not compile. You must use a full if statement. This was a deliberate design choice to keep the language simple and avoid ambiguous nesting.
A common error is forgetting that variables declared in the short statement are out of scope after the if block. Trying to access v outside the block in the earlier math.Sqrt example would cause a compile-time error.
Another subtle point involves shadowing. If you have a variable in the outer scope and redeclare it in the short statement using :=, you create a new variable that shadows the outer one only inside the if block. When the block exits, the outer variable is unchanged. This can mask bugs when you meant to modify the outer variable.
count := 5
if count := 10; count > 8 {
fmt.Println(count) // 10
}
fmt.Println(count) // 5 – original value preserved
To avoid this, when you intend to modify the outer variable, use a plain assignment (=) rather than a short declaration inside the if.
Summary
Go's if statement strips conditional logic down to essentials: a boolean condition, mandatory braces, and optional short‑lived initialization. The absence of a ternary operator and the strict brace requirements are intentional constraints that eliminate entire categories of bugs and stylistic arguments. The short statement form encourages tight scoping and is the backbone of Go's error‑handling idiom. When you need to choose among many distinct values, switch often leads to cleaner code—but if remains the right tool for boolean decisions, range checks, and guard clauses.