Golang uses if for conditional decisions. Conditions do not need parentheses, but braces are required. This keeps code visually consistent and avoids ambiguous one-line conditionals.
Use if for validation, branching business rules, permission checks, and early exits. Golang code often handles errors and invalid states early, then lets the main successful path continue with less indentation.
package main
import "fmt"
func main() {
score := 82
if score >= 90 {
fmt.Println("Excellent")
} else if score >= 70 {
fmt.Println("Good")
} else {
fmt.Println("Keep practicing")
}
}
An if statement can include a short statement before the condition. Variables declared there are scoped only to the if, else if, and else blocks.
This is common when you call a function and immediately check the result, such as parsing input, loading a record, or checking a map lookup.
package main
import (
"fmt"
"strconv"
)
func main() {
if limit, err := strconv.Atoi("25"); err != nil {
fmt.Println("invalid limit:", err)
} else {
fmt.Println("limit:", limit)
}
// limit and err are not available here.
}
A guard clause checks for a problem and returns early. This style is common in Golang because errors are explicit values. It keeps the successful path flatter and easier to read.
Instead of wrapping the entire function in a large else block, handle invalid input, missing data, or errors immediately.
func publishTutorial(title string, lessons int) error {
if title == "" {
return errors.New("title is required")
}
if lessons <= 0 {
return errors.New("lessons must be greater than zero")
}
return savePublishedTutorial(title, lessons)
}
A switch is useful when a value can match several cases. Golang automatically exits after the matching case, so you normally do not write break.
Multiple values can share one case. Use default for values that do not match any explicit case. A switch can often be clearer than a long chain of related else if checks.
| Feature | Golang Behavior |
|---|---|
| No automatic fallthrough | The matching case exits automatically. |
| Multiple values | One case can match several values. |
| Default case | Runs when no other case matches. |
package main
import "fmt"
func main() {
role := "editor"
switch role {
case "admin":
fmt.Println("Full access")
case "editor", "author":
fmt.Println("Can manage content")
default:
fmt.Println("Read only")
}
}
A switch can be written without a target value. In that form, each case is a boolean condition. This is useful when several related conditions would otherwise become a noisy if chain.
Keep condition-only switches focused. If cases are unrelated, separate if statements may communicate intent better.
func describeScore(score int) string {
switch {
case score >= 90:
return "excellent"
case score >= 70:
return "good"
case score >= 40:
return "pass"
default:
return "needs practice"
}
}
Golang has one loop keyword: for. It can behave like a classic counter loop, a while loop, or an infinite loop. This keeps looping syntax compact without needing separate keywords.
Use break to leave a loop early and continue to skip the current iteration. Keep loop bodies small enough that the exit conditions remain easy to read.
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
fmt.Println("counter:", i)
}
attempts := 0
for attempts < 2 {
attempts++
}
for {
fmt.Println("runs until break")
break
}
}
range is used to loop over slices, arrays, maps, strings, and channels. With slices and arrays it returns index and value. With maps it returns key and value.
If you do not need one returned value, assign it to _. This makes the intent clear and avoids unused-variable errors. Remember that map iteration order is not guaranteed.
package main
import "fmt"
func main() {
courses := []string{"Golang", "TypeScript", "Java"}
for index, course := range courses {
fmt.Println(index+1, course)
}
views := map[string]int{"golang": 1200, "typescript": 1800}
for slug, count := range views {
fmt.Println(slug, count)
}
}
defer schedules a call to run when the surrounding function returns. Deferred calls are commonly used to close files, unlock mutexes, stop timers, or finish trace spans.
Place defer near the line that acquires the resource. That habit makes cleanup hard to forget and keeps the lifecycle readable. Deferred calls run in last-in, first-out order.
func readFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return io.ReadAll(file)
}
if keep temporary variables scoped to the condition.
switch exits automatically after the matching case.
for is the only loop keyword in Golang.
range is the standard way to iterate collections.
defer is useful for reliable cleanup.
Explore 500+ free tutorials across 20+ languages and frameworks.