跪拜 Guibai
← All articles
Backend · Go

Go's One Loop to Rule Them All: for, range, break, and the goto You Shouldn't Use

By 小满zs ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Developers coming from languages with separate `while` and `do-while` constructs often overcomplicate Go loops. The single-keyword design forces a consistent mental model, and labeled breaks eliminate a whole class of loop-control flags that make nested iteration harder to read.

Summary

Go has no `while` or `do-while` — every loop is a `for` loop with optional init, condition, and post statements. Dropping the init and post gives a while-style loop; dropping the condition creates an infinite loop that must be broken manually. The `range` clause handles slices, arrays, maps, and strings, returning an index (or key) and a value on each iteration. When ranging over a string, the index steps by byte position while the value is a full Unicode code point, so multi-byte characters cause the index to jump.

Control flow inside loops uses `break` to exit the current loop immediately and `continue` to skip the rest of the current iteration. Labeled breaks let you jump out of a specific outer loop in nested structures, a feature that avoids flag variables. The `goto` keyword exists but is discouraged outside of narrow error-cleanup patterns; labeled `break` and `continue` cover most multi-level control needs with better readability.

Conditional logic in Go follows the standard `if`/`else if`/`else` chain, with `switch` as a cleaner alternative when matching a single expression against multiple fixed values. Go's `switch` does not fall through by default, so no `break` is needed between cases. Logical operators `&&`, `||`, and `!` combine boolean expressions in the usual short-circuiting manner.

Takeaways
Go has exactly one loop keyword: `for`. All loop shapes — counted, while-style, infinite — are expressed by including or omitting its three clauses.
Omitting the init and post statements from a `for` loop produces a while-style loop; omitting the condition as well creates an infinite loop.
The `range` clause returns two values per iteration: an index (or map key) and the element. Use `_` to discard the one you don't need.
Ranging over a string yields Unicode code points (runes), not bytes. The index advances by byte count, so multi-byte characters cause gaps in the index sequence.
Map iteration order with `range` is not guaranteed and can vary between runs.
`break` exits the innermost loop immediately; `continue` skips the rest of the current iteration and proceeds to the next.
Labeled `break` and `continue` let you control outer loops from inside nested loops without flag variables.
`goto` works but is discouraged in routine code; labeled loop control is the idiomatic alternative.
Go's `switch` does not fall through by default — no `break` is required between cases.
Logical operators `&&`, `||`, and `!` short-circuit as expected and combine boolean conditions in `if` and loop guards.
Conclusions

Labeled breaks are an underused feature that can flatten deeply nested loop logic. Instead of setting a `done` flag and checking it at every level, a single `break Outer` exits the exact scope you want, making the intent explicit.

The fact that ranging over a string gives runes but byte-based indices is a common source of off-by-one confusion for newcomers. Printing the index alongside the character, as the tutorial does, is a quick way to internalize the difference between byte position and character count.

Go's decision to make `switch` non-fallthrough by default removes an entire category of bugs that C-style languages suffer from when a missing `break` causes unintended case execution.

Concepts & terms
rune
Go's representation of a Unicode code point, equivalent to an int32. When ranging over a string, each iteration yields a rune rather than a byte, so multi-byte characters like Chinese hanzi are handled as single units.
labeled break/continue
A Go feature that lets you attach a label to a loop and then break or continue that specific labeled loop from within nested loops. This avoids the need for boolean flags to propagate exit conditions upward through multiple loop levels.
short-circuit evaluation
The behavior of logical operators `&&` and `||` where the second operand is not evaluated if the first operand already determines the result. In Go, `a && b` skips `b` if `a` is false; `a || b` skips `b` if `a` is true.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗