Go Labeled Break and Continue — Find the Bug¶
Instructions¶
Each exercise contains buggy Go code involving a label, break, or continue. Identify the bug, explain why, and provide the corrected code. Difficulty: 🟢 Easy, 🟡 Medium, 🔴 Hard.
Bug 1 🟢 — Unused Label (Compile Error)¶
Solution
**Bug**: The label `Outer` is declared but never referenced. Go rejects this: **Fix** (option A — remove the label): **Fix** (option B — use it): **Key lesson**: Go enforces that every declared label has at least one branch referencing it. This prevents stale labels from accumulating.Bug 2 🟢 — break Inside for { select { } }¶
package main
import (
"fmt"
"time"
)
func main() {
quit := make(chan struct{})
go func() {
time.Sleep(20 * time.Millisecond)
close(quit)
}()
for {
select {
case <-quit:
fmt.Println("quitting")
break
default:
// busy work
}
}
fmt.Println("after loop")
}
Solution
**Bug**: `break` exits the `select`, not the `for`. After printing "quitting", the `for` re-enters the `select`. Because `quit` is closed, the case keeps firing and the loop spins indefinitely (or, with a different pattern, blocks forever). The line `fmt.Println("after loop")` never runs. **Fix**: Or use `return` from a function body where the loop is the last action. **Key lesson**: `break` inside `select` exits the `select` only. Use a label on the surrounding `for` to escape both.Bug 3 🟢 — Surprising break Inner That Exits a Switch¶
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
Inner:
switch i {
case 0:
fmt.Println("zero")
case 1:
fmt.Println("one")
break Inner // works, but a plain break would do the same
default:
fmt.Println("other")
}
}
}
Solution
**Bug**: `break Inner` works, but it is misleading. Inside a `case` body, a plain `break` already exits the `switch`. The label `Inner` adds no behavior here. **The bug is conceptual**: the reader may believe the label changes the behavior (e.g., breaks out of an outer for). It does not. **Fix** (option A — remove the unnecessary label): **Fix** (option B — if you DID intend to break the outer for): **Key lesson**: `break Label` where the label is on a `switch` works but is rarely what you want. Verify what the label names.Bug 4 🟢 — continue Outer Targeting a Switch¶
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
Inner:
switch i {
case 0:
fmt.Println("zero")
case 1:
continue Inner // BUG
}
}
}
Solution
**Bug**: `continue` requires a label on a **for** loop. `Inner` here labels a `switch`. Compile error: **Fix** (move the label to the surrounding for): Now `continue Outer` advances the for loop. **Key lesson**: `continue` is for `for` only. Labels on `switch`/`select` cannot be `continue` targets.Bug 5 🟢 — Plain break In Nested Loop¶
package main
import "fmt"
func main() {
grid := [][]int{
{1, 2, 3},
{4, 5, 6},
}
target := 5
var ri, ci int
for i, row := range grid {
for j, v := range row {
if v == target {
ri, ci = i, j
break // BUG: only exits inner loop
}
}
}
fmt.Println(ri, ci) // 1 1, but inner loop completes after break
}
Solution
**Bug**: `break` exits only the inner loop. The outer `for i, row := range grid` continues, potentially overwriting `ri, ci` if a later match is found, or just doing wasted work scanning remaining rows. In this small example, the result happens to be correct, but for a grid with multiple `5`s the LAST match wins, not the FIRST. **Fix** — labelled break: Or extract: **Key lesson**: Plain `break` exits only the innermost enclosing for. For nested loops, use a label or extract.Bug 6 🟡 — Trying To Break Out of an Outer Function¶
package main
import "fmt"
func main() {
Outer:
for i := 0; i < 3; i++ {
process(i)
}
fmt.Println("done")
}
func process(i int) {
if i == 1 {
// We want to abort the entire main loop here.
// break Outer // ERROR: label Outer is not in scope
}
}
Solution
**Bug**: Labels are function-scoped. `Outer` is declared in `main`; `process` cannot reference it. **Fix** — use a return signal: Or use a `context.Context` for cancellation across goroutine boundaries: **Key lesson**: Labels cannot escape a function. Use return values or context cancellation for cross-function control flow.Bug 7 🟡 — Same Label Re-Declared¶
package main
import "fmt"
func main() {
Outer:
for i := 0; i < 3; i++ {
if i == 1 {
break Outer
}
fmt.Println(i)
}
Outer: // BUG
for j := 0; j < 3; j++ {
if j == 1 {
break Outer
}
fmt.Println(j)
}
}
Solution
**Bug**: Two labels named `Outer` in the same function. Compile error: Labels are unique within a function regardless of scope position — even though the two loops do not overlap, the label name conflicts. **Fix** — use distinct names: **Key lesson**: Labels are function-scoped and unique by name within the function. Two non-overlapping loops cannot share a label name.Bug 8 🟡 — Label On a Plain Block¶
Solution
**Bug**: Labels for `break`/`continue` must precede a `for`, `switch`, or `select`. A label on a block is only valid as a `goto` target, and even then `break Outer` is not allowed. Compile error: **Fix** (option A — use a `for`): **Fix** (option B — use `goto`, which CAN target a labelled block): **Key lesson**: `break L` and `continue L` only work when `L` labels `for`/`switch`/`select`. Labels on blocks are only useful for `goto`.Bug 9 🟡 — Defer In a Labelled Loop Body¶
package main
import (
"fmt"
"os"
)
func main() {
Loop:
for i := 0; i < 3; i++ {
f, err := os.Open("/etc/hosts")
if err != nil {
break Loop
}
defer f.Close() // BUG
if i == 1 {
break Loop
}
fmt.Println("opened", i)
}
fmt.Println("done")
}
Solution
**Bug**: `defer f.Close()` registers a deferred call for FUNCTION exit, not loop iteration exit. Defers accumulate inside the loop. After three iterations, three `Close` calls are pending. Worse: the second `os.Open` could fail because the first file is still open (depending on resource limits). **Fix** — close per-iteration explicitly:Loop:
for i := 0; i < 3; i++ {
if err := process(i); err != nil {
break Loop
}
fmt.Println("opened", i)
}
func process(i int) error {
f, err := os.Open("/etc/hosts")
if err != nil {
return err
}
defer f.Close() // function-scoped: closes at process return
if i == 1 {
return errors.New("abort")
}
return nil
}
Bug 10 🔴 — Label Used Across Goroutine Boundary¶
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
Outer:
for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
if i == 1 {
// break Outer // ERROR: label not in scope
fmt.Println("would break")
}
fmt.Println(i)
}(i)
}
wg.Wait()
}
Solution
**Bug**: Labels are function-scoped. The goroutine body is a separate function (an anonymous one); it cannot reference `Outer`. This compiles only because `break Outer` is commented out. Uncommenting it yields: **Fix** — use a cancellation signal: **Key lesson**: Labels cannot cross function boundaries. For cross-goroutine signalling, use channels or contexts.Bug 11 🔴 — continue In a for { select { ... } } Targeting the Wrong Loop¶
package main
import "fmt"
func main() {
items := []int{1, 2, 3, 4, 5}
for _, x := range items {
select {
case <-time.After(0):
if x%2 == 0 {
continue // BUG: continues the for-range, but is it intentional?
}
fmt.Println(x)
}
}
}
Solution
**Discussion**: A bare `continue` inside `for { select { } }` (with `for` having an iteration variable) targets the for. This is correct in this snippet — `continue` advances `range items`. The bug is only in code style: it can be unclear to readers. If the writer wanted to advance to the next `select` case in a `for { select { ... } }` shape, that is meaningless — `select` already exits after one case. **Clarification**: The label makes intent obvious. **Key lesson**: Inside `for { select { } }`, plain `continue` advances the `for`. Use a label to make intent explicit when the structure is large.Bug 12 🔴 — break In a Labelled Switch Inside a For¶
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
Sw:
switch i {
case 0, 1:
fmt.Println("low")
case 2:
fmt.Println("two")
break Sw // unnecessary label
case 3:
fmt.Println("three")
// The author wanted to ALSO exit the for here.
break Sw // BUG: only exits the switch
case 4:
fmt.Println("four")
}
}
fmt.Println("done")
}
Solution
**Bug**: At `case 3`, the author intended to break out of the for entirely, but `break Sw` only exits the switch (the label is on the switch). Output: **Fix** — label the for: Output: **Key lesson**: A label sits where you place it. If you want to break the for, label the for, not the switch.Bonus Bug 🔴 — Forgetting Label After Refactor¶
package main
import "fmt"
func main() {
items := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
var found int
Search:
for _, row := range items {
for _, v := range row {
if v == 5 {
found = v
// After refactor, this break used to be `break Search`
break
}
}
}
fmt.Println(found)
}