break, continue & Labeled Control Flow
break and continue adjust loop and select flow, while labels let you target an outer for, switch, or select when nested logic would otherwise need flags or duplicated conditions.
Search across all documentation pages
break and continue adjust loop and select flow, while labels let you target an outer for, switch, or select when nested logic would otherwise need flags or duplicated conditions.
Used sparingly, labels clarify intent; overused, they obscure structure.
break exits the innermost for, switch, or select.
continue skips to the next iteration of the innermost loop.
A label on a loop or select lets break label or continue label target that statement.
break inside select only exits the select, not the surrounding loop, unless labeled.
Refactoring into a helper function is often clearer than deep labels.
Quick-reference recipe card - copy-paste ready.
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
if i == 2 {
continue // skip printing 2
}
if i == 4 {
break // stop before 4
}
fmt.Println(i)
}
}When to reach for this:
continue to skip irrelevant iterations without nesting another if body.break when further iterations cannot change the outcome (found target, error).break to leave a search in nested loops without found booleans.package main
import (
"fmt"
"time"
)
func findPair(matrix [][]int, target int) (int, int, bool) {
search:
for r, row := range matrix {
for c, v := range row {
if v == target {
return r, c, true
}
if v < 0 {
break search // abort entire search on sentinel
}
}
}
return 0, 0, false
}
func poll(ctxDone <-chan struct{}) {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
loop:
for {
select {
case <-ctxDone:
break loop // leave the for, not just select
case <-ticker.C:
fmt.Println("tick")
}
}
fmt.Println("stopped")
}
func main() {
m := [][]int{{1, 2}, {-1, 9}, {3, 4}}
r, c, ok := findPair(m, 9)
fmt.Println("found", r, c, ok)
done := make(chan struct{})
go func() {
time.Sleep(250 * time.Millisecond)
close(done)
}()
poll(done)
}What this demonstrates:
break search exits nested loops on a sentinel value.(r, c, true) is an alternative to labeled break when results are needed.break loop on a select inside for exits the outer loop, not just the select.continue is not shown here but would skip to the next ticker iteration similarly.break and continue apply to the innermost active construct of their kind.for, switch, or select statement - not arbitrary blocks.goto exists in Go but is discouraged except in rare code generators and low-level optimizations.return exits the entire function, which often replaces labeled breaks in helpers.select with default plus break can busy-loop if you forget to block or sleep.| Statement | Effect |
|---|---|
break | Exit innermost for/switch/select |
break Label | Exit labeled for/switch/select |
continue | Next iteration of innermost for |
continue Label | Next iteration of labeled for |
return | Exit current function |
for {
select {
case <-ctx.Done():
break loop // exits for, not only select
case v := <-ch:
handle(v)
}
}
Without the label, break would only exit select and immediately re-enter the for.
// Prefer early return over labeled break when computing a result
func contains(haystack []string, needle string) bool {
for _, s := range haystack {
if s == needle {
return true
}
}
return false
}for { select { case <-done: break } } loops forever. Fix: use break loop with a label on the for, or return.continue targets loops only, not switch. Fix: restructure or use return.found booleans with multiple break levels confuse readers. Fix: label once or extract a function.continue before rate limiting causes tight loops. Fix: put throttling after the continue check or at loop end.break does not cross function boundaries. Fix: use return from the helper.| Alternative | Use When | Don't Use When |
|---|---|---|
Helper function + return | Search with a clear result | Hot inner loop where inlining matters |
errors.Err / sentinel error | Break out of deep call stack | Simple nested loops |
| Context cancellation | Stop workers cooperatively | Pure in-memory matrix search |
Iterator yield false (Go 1.23+) | Custom sequences | Hand-written nested for |
| Map/set membership test | Avoid nested scan | Need coordinates of match |
No - only the innermost for, switch, or select.
Use return to leave the function.
No - labels apply to for, switch, and select only.
Wrap logic in a labeled for or extract a function.
break targets the select statement itself.
Add a label on the for and break forLabel.
No - continue applies to loops.
Use fallthrough rarely, or restructure with loops.
When a helper with early return reads cleaner.
Labels are for tight inner loops where function calls add noise.
It exits the loop block, not the function.
Defers still run when the surrounding function returns.
Use continue outerLabel on the outer for.
Or restructure with a function and return.
Rarely - mostly in generated code or performance-critical inner loops.
Prefer labels or functions in application code.
Labels on intermediate for loops or refactor into functions.
Deeply nested selects signal a design smell.
Yes - it exits the for range entirely.
continue skips to the next element.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026