You write a function to apply a 10% price rise across a slice of products: for _, item := range items { item.Price *= 1.10 }. It compiles clean, runs without error, and does absolutely nothing — print the slice afterward and every price is unchanged. There's no crash to point at, which is exactly why this one survives into production.
for _, it := range items {
it.Price *= 1.10 // mutates a COPY of the element, not items itself
}
fmt.Println(items) // unchanged — every price is exactly what it wasThe fix is in the last for variant below: range hands you a copy of each value, so to change the original you index back into the slice instead.
Go simplifies control flow down to three keywords: if, for, and switch. There is no while or do-while — for alone covers counted loops, condition-only loops, and infinite loops, depending on which clauses you omit. Parentheses around conditions are not just optional but idiomatically omitted; braces are mandatory even for single-statement bodies.
for: Go's only loop
The same for keyword handles every loop shape by simply omitting parts of its three clauses (init; condition; post):
for i := 0; i < 5; i++ { // classic counted loop
fmt.Println(i)
}
n := 0
for n < 3 { // condition only — this IS Go's "while"
n++
}
for { // no clauses — infinite loop
if n > 10 { break }
n++
}
for i, v := range []string{"a", "b", "c"} { // range over a slice
fmt.Println(i, v) // i = index, v = value
}That last form is also the fix for the price-rise bug at the top of this page. v (or it) in a range clause is always a fresh copy of the element; to mutate the original you range for the index and write back through it:
for i := range items {
items[i].Price *= 1.10 // indexes into the slice itself, not a copy
}
fmt.Println(items) // every price raised 10%, for real this timeif statements and the init shortcut
if never uses parentheses around its condition, and can carry an optional initializer statement before the condition, scoped only to the if/else chain — a pattern used constantly for error handling:
if err := doSomething(); err != nil {
return err // err is scoped to this if/else block only
}
if x := compute(); x > 10 {
fmt.Println("big")
} else if x > 0 {
fmt.Println("small")
} else {
fmt.Println("non-positive")
}switch without fallthrough
Go's switch breaks from the case automatically after each match — the opposite default of C or Java, where you must remember break. Falling through to the next case requires the explicit fallthrough keyword. A switch with no expression at all acts as a clean chain of if/else conditions, and switches can match multiple values or types in one case.
switch day {
case "Sat", "Sun":
fmt.Println("weekend")
default:
fmt.Println("weekday")
}
switch { // no expression: acts like if/else if/else
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
default:
fmt.Println("C or below")
}