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.
Used sparingly, labels clarify intent; overused, they obscure structure.
Summary
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.
Recipe
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:
- Use
continueto skip irrelevant iterations without nesting anotherifbody. - Use
breakwhen further iterations cannot change the outcome (found target, error). - Use labeled
breakto leave a search in nested loops withoutfoundbooleans. - Extract a function when more than one label or multiple exits make the flow hard to scan.
Working Example
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:
- Labeled
break searchexits nested loops on a sentinel value. - Returning
(r, c, true)is an alternative to labeled break when results are needed. break loopon aselectinsideforexits the outer loop, not just theselect.continueis not shown here but would skip to the next ticker iteration similarly.
Deep Dive
How It Works
breakandcontinueapply to the innermost active construct of their kind.- Labels must be attached to a
for,switch, orselectstatement - not arbitrary blocks. gotoexists in Go but is discouraged except in rare code generators and low-level optimizations.returnexits the entire function, which often replaces labeled breaks in helpers.selectwithdefaultplusbreakcan busy-loop if you forget to block or sleep.
break vs continue vs return
| 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 |
select + for Pattern
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.
Go Notes
// 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
}Gotchas
- break only exits select -
for { select { case <-done: break } }loops forever. Fix: usebreak loopwith a label on thefor, orreturn. - Labeled continue on switch -
continuetargets loops only, notswitch. Fix: restructure or usereturn. - Flag variables instead of labels -
foundbooleans with multiplebreaklevels confuse readers. Fix: label once or extract a function. - continue skips delay logic - Placing
continuebefore rate limiting causes tight loops. Fix: put throttling after thecontinuecheck or at loop end. - Breaking from defer -
breakdoes not cross function boundaries. Fix: usereturnfrom the helper. - goto as a substitute - Unlabeled jumps harm readability. Fix: prefer structured control flow and functions.
Alternatives
| 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 |
FAQs
Does break exit the function?
No - only the innermost for, switch, or select.
Use return to leave the function.
Can I label an if statement?
No - labels apply to for, switch, and select only.
Wrap logic in a labeled for or extract a function.
Why does break inside select not stop my loop?
break targets the select statement itself.
Add a label on the for and break forLabel.
Is continue valid in a switch?
No - continue applies to loops.
Use fallthrough rarely, or restructure with loops.
When should I avoid labels?
When a helper with early return reads cleaner.
Labels are for tight inner loops where function calls add noise.
Does labeled break run defer statements?
It exits the loop block, not the function.
Defers still run when the surrounding function returns.
Can I continue an outer loop from an inner if?
Use continue outerLabel on the outer for.
Or restructure with a function and return.
Is goto ever idiomatic?
Rarely - mostly in generated code or performance-critical inner loops.
Prefer labels or functions in application code.
How do I break out of two nested selects?
Labels on intermediate for loops or refactor into functions.
Deeply nested selects signal a design smell.
Does break work in range loops?
Yes - it exits the for range entirely.
continue skips to the next element.
Related
- for Loop Variants & range Semantics - loops you break out of
- Control Flow Basics - introductory break/continue
- Go Control Flow: Expression-Oriented Design - flat flow guidance
- range Variable Reuse Pitfalls - closures inside loops you may continue past
- Control Flow Best Practices - refactor nested logic
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).