Table of contents
- if — With an Initializer Statement
- switch — No Fallthrough
- for — Go’s Only Loop
- break, continue, and Labels
if — With an Initializer Statement
Go’s if isn’t drastically different from other languages. However, it has one unique feature: you can add an initializer statement before the condition.
package main
import (
"fmt"
"strconv"
)
func main() {
// Standard if
x := 10
if x > 5 {
fmt.Println("x is greater than 5")
}
// if with initializer statement
if n, err := strconv.Atoi("42"); err == nil {
fmt.Println("conversion succeeded:", n)
} else {
fmt.Println("conversion failed:", err)
}
// n and err are not accessible here
}
if n, err := strconv.Atoi("42"); err == nil — the part before the semicolon is the initializer, and the part after is the condition. Variables declared this way are only available within the if-else block and disappear outside of it.
Why is this pattern useful? Go has many functions that return errors. Being able to bundle the result check and handling into one block keeps the scope clean. This pattern appears constantly in real-world code, so it’s worth getting familiar with.
Looking at the execution flow of an if with an initializer statement, the scope boundaries become clear.
flowchart LR
Start([if entry]) --> Init["Execute initializer<br/>n, err := strconv.Atoi(...)"]
Init --> Cond{"err == nil ?"}
Cond -->|true| Then["then block<br/>n accessible"]
Cond -->|false| Else["else block<br/>err accessible"]
Then --> End([if exit — n, err gone])
Else --> End
Note that Go doesn’t use parentheses around if conditions. You can write them, but gofmt will automatically strip them out.
switch — No Fallthrough
Most developers working with C-family languages have experienced bugs from forgetting break in a switch statement. Go solves this problem at its root: each case automatically breaks.
package main
import "fmt"
func main() {
day := "Tue"
switch day {
case "Mon":
fmt.Println("Start of the week")
case "Tue", "Wed", "Thu":
fmt.Println("Midweek")
case "Fri":
fmt.Println("TGIF!")
default:
fmt.Println("Weekend")
}
}
You can list multiple values in a single case separated by commas. Since there’s no need for break, the code is concise and there’s less room for mistakes.
If you do want C-like fallthrough to the next case, you can explicitly write fallthrough. But in practice, using fallthrough is extremely rare.
Go’s switch has an even more powerful feature: the conditionless switch.
package main
import "fmt"
func main() {
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("F")
}
}
When you omit the value after switch, each case is evaluated as an independent condition. This is often more readable than a long chain of if-else if-else if. This pattern particularly shines when you have three or more conditions in a row.
for — Go’s Only Loop
Go has no while and no do-while. for is the only loop. Instead, for transforms into several different forms.
package main
import "fmt"
func main() {
// 1. Traditional for
for i := 0; i < 5; i++ {
fmt.Print(i, " ")
}
fmt.Println()
// 0 1 2 3 4
// 2. Using it like while
count := 0
for count < 3 {
fmt.Print(count, " ")
count++
}
fmt.Println()
// 0 1 2
// 3. Infinite loop
n := 0
for {
if n >= 3 {
break
}
fmt.Print(n, " ")
n++
}
fmt.Println()
// 0 1 2
}
The third form, for {}, is an infinite loop. It’s commonly used for server main loops or event processing. It serves the same role as while(true) in other languages, and in Go, this is the idiomatic way to express it.
When iterating over collections, use range.
package main
import "fmt"
func main() {
fruits := []string{"apple", "banana", "cherry"}
for i, fruit := range fruits {
fmt.Printf("%d: %s\n", i, fruit)
}
// If you don't need the index, discard it with _
for _, fruit := range fruits {
fmt.Println(fruit)
}
}
range returns both the index and the value simultaneously. Ignoring the index with _ (the blank identifier) is an extremely common pattern in Go. Since declaring a variable and not using it causes a compile error, without _ you’d be forced to use unnecessary variables.
break, continue, and Labels
break and continue work the same as in other languages. They exit the loop or skip to the next iteration.
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i == 3 {
continue // Skip 3
}
if i == 7 {
break // Stop at 7
}
fmt.Print(i, " ")
}
fmt.Println()
// 0 1 2 4 5 6
}
When you want to control an outer loop from within a nested loop, use a label.
package main
import "fmt"
func main() {
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer // Break out of the outer loop at once
}
fmt.Printf("(%d, %d) ", i, j)
}
}
fmt.Println()
// (0, 0) (0, 1) (0, 2) (1, 0)
}
outer: is the label, and break outer breaks out of the loop that label is attached to. Java has a similar feature, but few people know about it. In Go, this pattern is used naturally when breaking out of nested loops is needed.
Labels can also be used with continue.
package main
import "fmt"
func main() {
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer // Skip to the next iteration of the outer loop
}
fmt.Printf("(%d, %d) ", i, j)
}
}
fmt.Println()
// (0, 0) (1, 0) (2, 0)
}
Overusing labels makes code complex, so use them only when truly needed. In most cases, extracting the nested loop into a separate function is cleaner.
The next part covers Go functions. We’ll explore multiple return values, closures, first-class functions, and other features unique to Go functions.




Loading comments...