Go Labeled Break and Continue — Junior Level¶
1. Introduction¶
What is it?¶
A labeled break or labeled continue is a break or continue statement that names the loop (or switch/select) it should affect. Without a label, break exits the innermost enclosing for, switch, or select, and continue advances the innermost enclosing for. With a label, you can target an outer statement directly.
How to use it?¶
package main
import "fmt"
func main() {
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i*j > 2 {
break Outer
}
fmt.Println(i, j)
}
}
}
break Outer exits the labelled outer loop, not just the inner one.
2. Prerequisites¶
- For-loop basics (2.5.1)
- Plain
breakandcontinue(2.5.3, 2.5.4) - Switch and select statements
- Function scope rules
3. Glossary¶
| Term | Definition |
|---|---|
| label | An identifier followed by : that names a for, switch, or select |
| labeled break | break L — exits the labelled statement |
| labeled continue | continue L — starts the next iteration of the labelled for |
| target | The statement that a label names |
| function-scoped | Labels are visible only inside the function where they are declared |
| unused label | A label declared but never referenced — a compile error in Go |
| nested loop | A loop inside another loop |
4. Core Concepts¶
4.1 Label Syntax¶
A label is an identifier followed by :, placed immediately before a for, switch, or select:
By convention, label names start with a capital letter (Outer, Loop, Done), though any identifier is valid.
4.2 Labeled break¶
break L exits the statement labelled L. It can target a for, switch, or select.
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if condition {
break Outer // jumps out of the i loop entirely
}
}
}
// execution continues HERE
Without Outer, break would only exit the inner for j loop.
4.3 Labeled continue¶
continue L starts the next iteration of the for loop labelled L. The label MUST point to a for (not switch or select).
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if condition {
continue Outer // i++ runs, then i loop continues
}
}
}
4.4 Labels Are Function-Scoped¶
A label is visible only in the function in which it is declared. You cannot break or continue to a label in another function.
func main() {
Outer:
for i := 0; i < 3; i++ {
helper() // helper cannot reach Outer
}
}
func helper() {
// break Outer // ERROR: label Outer not defined
}
4.5 Unused Labels Are a Compile Error¶
If you declare a label and never use it, the compiler rejects the program:
This rule prevents stale labels from accumulating in the codebase.
4.6 Labels Apply Only to for, switch, or select¶
A label must directly precede a for, switch, or select. You cannot label a regular block, an if, or a function body.
4.7 continue Requires a for Label¶
Even though you can label any of for, switch, or select, continue only works on a label that names a for. Targeting a switch or select label with continue is a compile error.
5. Real-World Analogies¶
Multi-floor elevator: a plain break lets you off on the current floor; a labeled break lets you exit the entire building. A label is like marking a floor as "ground level — exit here."
Nested boxes: imagine boxes inside boxes. A regular break opens the next box outward (one level). A labeled break opens any specifically named box.
Email subject filters: when scanning inboxes, a "skip this thread" matches labeled continue (move on to the next outer thread). A "stop scanning" matches labeled break.
6. Mental Models¶
Outer:
for i := 0; i < N; i++ { ← `Outer` names this loop
for j := 0; j < M; j++ { ← inner loop
...
break Outer ← jump to AFTER the outer loop
continue Outer ← next i, restart inner loop
}
}
// execution lands HERE after `break Outer`
A label is just a marker. break L and continue L are jumps; the compiler checks that L names a valid target.
7. Pros & Cons¶
Pros¶
- Clear way to exit nested loops without a flag variable
- Readable when used sparingly
- Matches well-known idioms in compilers, parsers, and graph traversals
- Removes the need for
gotoin many cases
Cons¶
- Can hide complex control flow if abused
- Often a sign that the inner block should be extracted into a function
- Unused labels are compile errors — surprising for new Go developers
- Labels named on
switch/selectcannot becontinue-targets, which is easy to forget
8. Use Cases¶
- Exit a nested loop early (search found a match)
- Skip the rest of an outer iteration on a condition
- Exit a
selectfrom inside a deeply nested branch - Walk a 2-D grid with early termination
- Parser state machines that step through tokens
- Tic-tac-toe / Sudoku board scans
- Match-and-break patterns in lexers
selectinsideforloops where theselect-internalbreakonly exits theselect
9. Code Examples¶
Example 1 — Plain Search in a Grid¶
package main
import "fmt"
func main() {
grid := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
target := 5
foundI, foundJ := -1, -1
Search:
for i, row := range grid {
for j, v := range row {
if v == target {
foundI, foundJ = i, j
break Search
}
}
}
fmt.Println("at", foundI, foundJ) // at 1 1
}
Example 2 — Skipping a Row¶
package main
import "fmt"
func main() {
matrix := [][]int{
{1, 2, 3},
{4, -1, 6},
{7, 8, 9},
}
Outer:
for i, row := range matrix {
for _, v := range row {
if v < 0 {
continue Outer // skip the rest of this row
}
}
fmt.Println("row", i, "is non-negative")
}
}
Example 3 — Break From select Inside for¶
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
deadline := time.After(80 * time.Millisecond)
Loop:
for {
select {
case t := <-ticker.C:
fmt.Println("tick", t.Format("15:04:05.000"))
case <-deadline:
fmt.Println("done")
break Loop // a plain `break` would only leave the select
}
}
}
Example 4 — Tic-Tac-Toe Win Check¶
package main
import "fmt"
func main() {
board := [3][3]string{
{"X", "X", "X"},
{"O", " ", "O"},
{" ", "O", " "},
}
winner := ""
Rows:
for r := 0; r < 3; r++ {
if board[r][0] != " " && board[r][0] == board[r][1] && board[r][1] == board[r][2] {
winner = board[r][0]
break Rows
}
}
fmt.Println("winner:", winner)
}
Example 5 — Two-Pointer Scan¶
package main
import "fmt"
func main() {
a := []int{1, 3, 5, 7, 9}
b := []int{2, 4, 5, 8}
found := false
Outer:
for _, x := range a {
for _, y := range b {
if x == y {
fmt.Println("common:", x)
found = true
break Outer
}
}
}
if !found {
fmt.Println("no common element")
}
}
Example 6 — Continue To Outer Iteration¶
package main
import "fmt"
func main() {
items := [][]int{
{1, 2, 3},
{4, 5, -1, 6},
{7, 8, 9},
}
sums := []int{}
Group:
for _, group := range items {
sum := 0
for _, v := range group {
if v < 0 {
fmt.Println("skipping group with negative")
continue Group
}
sum += v
}
sums = append(sums, sum)
}
fmt.Println(sums) // [6 24]
}
10. Coding Patterns¶
Pattern 1 — Break Out of Both Loops on Match¶
Search:
for _, row := range data {
for _, v := range row {
if matches(v) {
result = v
break Search
}
}
}
Pattern 2 — Skip Outer Iteration on Bad Item¶
Outer:
for _, group := range groups {
for _, item := range group {
if invalid(item) {
continue Outer
}
}
// group is fully valid; process it
}
Pattern 3 — for { select { ... } } Quit¶
Pattern 4 — Multi-Level Break in a Parser¶
Tokens:
for {
tok := next()
switch tok.Kind {
case EOF:
break Tokens
case Comma:
continue
}
process(tok)
}
11. Clean Code Guidelines¶
- Use labels sparingly. They are powerful but can hide intent.
- Capitalize label names (
Outer,Loop) for visibility. - Place the label on its own line directly above the targeted statement.
- Prefer extraction when a labelled block grows beyond ~20 lines — pull the inner work into a function and use early
return. - Comment why the label is needed if the reason is non-obvious.
// Good
Outer:
for _, row := range grid {
for _, v := range row {
if v == target {
break Outer
}
}
}
// Worse — using a flag instead of a label
done := false
for _, row := range grid {
for _, v := range row {
if v == target {
done = true
break
}
}
if done {
break
}
}
The label version is shorter and clearer.
12. Product Use / Feature Example¶
A timeout-aware worker loop:
package main
import (
"context"
"fmt"
"time"
)
func runWorker(ctx context.Context, jobs <-chan int) {
Loop:
for {
select {
case <-ctx.Done():
fmt.Println("shutdown")
break Loop
case job, ok := <-jobs:
if !ok {
fmt.Println("jobs closed")
break Loop
}
fmt.Println("processed", job)
}
}
fmt.Println("worker exited")
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
defer cancel()
jobs := make(chan int)
go runWorker(ctx, jobs)
time.Sleep(50 * time.Millisecond)
}
The label Loop lets break escape both the select and the for.
13. Error Handling¶
Labelled break is often used to exit a loop that detected a fatal error:
var firstErr error
Loop:
for _, line := range lines {
for _, field := range strings.Fields(line) {
if !valid(field) {
firstErr = fmt.Errorf("invalid field %q", field)
break Loop
}
}
}
if firstErr != nil {
return firstErr
}
The label keeps the error path tight and easy to read.
14. Security Considerations¶
- Labelled jumps cannot leave a function. They cannot bypass
defer-installed cleanup; cleanup still runs at function return. - A
break Loopdoes not skip closing of resources opened earlier in the function —deferstill fires. - Avoid using labels to skip validation — always validate before the labelled jump rather than after.
15. Performance Tips¶
- No runtime cost vs. unlabeled break. The compiler emits the same control-flow edges.
- Use a label rather than a
doneflag — fewer instructions and clearer intent. - Don't introduce deep nesting just to use a label — refactor instead.
16. Metrics & Analytics¶
When monitoring loops that may break early:
var iterations int
Outer:
for _, x := range data {
for _, y := range x.items {
iterations++
if found(y) {
break Outer
}
}
}
fmt.Println("iterations:", iterations)
This style records work done before the early exit.
17. Best Practices¶
- Use labels for nested loop exit and for
for-selectquit. - Capitalize label names.
- Avoid unused labels (the compiler will reject them anyway).
- Prefer
break Lover flag variables. - Consider extracting into a helper function when nesting deepens.
- Remember:
continue LrequiresLto label afor.
18. Edge Cases & Pitfalls¶
Pitfall 1 — Forgetting That break In a for { select {} } Only Exits the select¶
Fix:
Pitfall 2 — Unused Label¶
Outer:
for i := 0; i < 3; i++ { // never references Outer
fmt.Println(i)
}
// compile error: label Outer defined and not used
Fix: remove the label, or add a break Outer / continue Outer.
Pitfall 3 — Label On a Block¶
Labels must precede for, switch, or select.
Pitfall 4 — continue On a Switch Label¶
continue requires a for label.
Pitfall 5 — Same Label Twice in One Function¶
func f() {
Outer:
for i := 0; i < 3; i++ { break Outer }
Outer: // ERROR: label Outer already defined
for j := 0; j < 3; j++ { break Outer }
}
Labels are unique per function.
19. Common Mistakes¶
| Mistake | Fix |
|---|---|
Plain break inside for { select { } } | Use a labeled break |
| Unused label | Use it or remove it |
continue on a switch label | Move the label to the surrounding for |
Label on an if or block | Place it before a for/switch/select |
| Trying to break out of a function via label | Use early return |
20. Common Misconceptions¶
Misconception 1: "A label is like a goto target." Truth: Labels for break/continue only allow jumps to the START or END of the labelled statement, not to arbitrary positions. goto is a separate construct (see 2.5.5).
Misconception 2: "Labels are visible across functions." Truth: Labels are function-scoped.
Misconception 3: "I can continue from inside a select." Truth: A bare continue inside for { select { ... } } works — it advances the for. But continue on a label that names a select is a compile error.
Misconception 4: "Using labels is a code smell." Truth: Labels are idiomatic in Go for exiting for-select loops and for nested-loop early exits. They become a smell only when nesting is excessive.
21. Tricky Points¶
break Lexits the labelled statement, not just the innermost.continue LrequiresLto label afor.- Labels are unique within a function and must be used.
breakinsidefor { select { ... } }only exits theselectunless labelled.- A label only applies to the very next
for/switch/select.
22. Test¶
package main
import "testing"
func findFirst(grid [][]int, target int) (int, int, bool) {
Outer:
for i, row := range grid {
for j, v := range row {
if v == target {
return i, j, true
}
_ = i
_ = j
if v == 0 {
continue Outer
}
}
}
return -1, -1, false
}
func TestFindFirst(t *testing.T) {
g := [][]int{{1, 0, 2}, {3, 4, 5}}
i, j, ok := findFirst(g, 4)
if !ok || i != 1 || j != 1 {
t.Errorf("got %d,%d,%v want 1,1,true", i, j, ok)
}
}
23. Tricky Questions¶
Q1: What does this print?
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue Outer
}
fmt.Println(i, j)
}
}
0 0, 1 0, 2 0. Each iteration of the inner loop runs only with j == 0, then continue Outer skips to the next i. Q2: Why does this loop forever?
A:break only exits the select; the for keeps looping. Use a label. Q3: Compile or run-time?
A: Compile error.continue requires a for label. 24. Cheat Sheet¶
// Break outer loop
Outer:
for ... {
for ... {
break Outer
}
}
// Continue outer loop
Outer:
for ... {
for ... {
continue Outer
}
}
// Break out of for { select { ... } }
Loop:
for {
select {
case <-quit:
break Loop
}
}
// Label conventions
// - Capitalized identifier
// - Placed directly above the for/switch/select
// - Function-scoped, must be used
25. Self-Assessment Checklist¶
- I can declare a label and break out of a nested loop
- I know
continue Lrequires aforlabel - I know labels are function-scoped
- I know unused labels are compile errors
- I can break out of
for { select { ... } }correctly - I avoid using labels when extraction is clearer
- I write capitalized label names
26. Summary¶
A label names a for, switch, or select. break L exits the labelled statement; continue L advances the labelled for. Labels are function-scoped and must be used. Common uses are nested-loop early exits and breaking out of for { select { ... } }. Use labels sparingly and prefer function extraction when nesting deepens.
27. What You Can Build¶
- Grid scanners with early termination
- Multi-level state machines
- Worker loops with quit channels
- Parsers that skip to the next token group
- Search routines on 2-D data
- Tic-tac-toe / Sudoku board checks
- Lexers that abort on the first error
- Stream processors with batch cutoff
28. Further Reading¶
- Go Spec — Break statements
- Go Spec — Continue statements
- Go Spec — Labeled statements
- Effective Go — Control structures
29. Related Topics¶
- 2.5.1 For Loop
- 2.5.3 Break
- 2.5.4 Continue
- 2.5.5 Goto
- 2.4 Switch and Select