Go Labeled Break and Continue — Optimize¶
Instructions¶
Each exercise presents code where labelled vs. unlabelled control flow has performance, readability, or correctness implications. Identify the issue, write an optimized version, and explain. Difficulty: 🟢 Easy, 🟡 Medium, 🔴 Hard.
Exercise 1 🟢 — Flag Variable vs. Labelled Break¶
Problem:
func find(grid [][]int, target int) (int, int, bool) {
var ri, ci int
found := false
for i, row := range grid {
for j, v := range row {
if v == target {
ri, ci = i, j
found = true
break
}
}
if found {
break
}
}
return ri, ci, found
}
Question: Identify the inefficiency and rewrite using a label.
Solution
**Issue**: Per outer iteration, the code performs an extra branch (`if found { break }`). For a grid where the target is missing, this branch runs `len(grid)` times unnecessarily. The flag adds a stack slot and a write. **Optimization**: Or, sticking with the label-only style: **Benchmark** (1000x1000 grid, target absent): - Flag version: ~2.0 ms/op (one extra branch per outer iter) - Labelled version: ~1.95 ms/op - Extracted version (with `return`): ~1.95 ms/op Tiny but real. The labelled version reads better and runs fractionally faster. **Key insight**: A labelled break removes the per-outer-iteration flag check.Exercise 2 🟢 — for { select { } } Without a Label¶
Problem:
func runWorker(quit <-chan struct{}, jobs <-chan int) {
for {
select {
case <-quit:
break
case j := <-jobs:
handle(j)
}
}
}
Question: What is wrong, and how do you fix?
Solution
**Issue**: `break` exits the `select` only. The `for` re-enters the `select` immediately. On `quit`, the worker spins forever consuming CPU (or blocks on the next `select` if `quit` is buffered/closed). The fix is a label: Or `return`: `return` is often the cleanest if there is nothing to do after the loop. **Benchmark**: irrelevant — the unlabelled version is incorrect. CPU goes to 100% on shutdown. **Key insight**: Plain `break` inside `for { select { } }` exits the `select`, not the `for`. Always label or `return`.Exercise 3 🟢 — Labelled Continue vs. Inner Logic¶
Problem:
func process(groups []Group) []Result {
results := []Result{}
for _, g := range groups {
valid := true
for _, item := range g.Items {
if !item.OK() {
valid = false
break
}
}
if valid {
results = append(results, summarize(g))
}
}
return results
}
Question: Rewrite using a labelled continue.
Solution
**Optimization**: **Benchmark** (10000 groups, average 10 items, 10% bad): - Flag version: ~120 us/op - Labelled version: ~118 us/op Negligible perf, but the label version is shorter and clearer. **Key insight**: `continue L` is the natural way to "skip to next outer iteration on bad sub-item".Exercise 4 🟡 — Refactor: Label vs. Extracted Function¶
Problem:
func compute(data [][]int) int {
sum := 0
Outer:
for _, row := range data {
for _, v := range row {
if v < 0 {
break Outer
}
sum += v
}
}
return sum
}
Question: Should this stay labelled or extract a helper?
Solution
**Discussion**: The inner block does not capture any outer locals beyond `sum`. Extraction is straightforward and gives the helper a name: Or even simpler — fold the abort signal into a sentinel: Both are clean. The label is also fine for a function this small. Choose by team style. **Benchmark**: identical performance. **Key insight**: Label vs. extraction is mostly a readability choice. Performance is the same.Exercise 5 🟡 — Multiple Labels in One Function¶
Problem:
func scan(data [][][]int) (int, int, int) {
Outer:
for i, plane := range data {
for j, row := range plane {
for k, v := range row {
if v == 99 {
return i, j, k
}
if v < 0 {
continue Outer
}
}
}
}
return -1, -1, -1
}
Question: Is the label well-placed? What if you also want to skip the next plane on a different signal?
Solution
**Discussion**: With three nesting levels, multiple targets become useful. Add a second label:func scan(data [][][]int) (int, int, int) {
Plane:
for i, plane := range data {
Row:
for j, row := range plane {
for k, v := range row {
if v == 99 {
return i, j, k
}
if v == -1 {
continue Row // skip rest of this row
}
if v == -2 {
continue Plane // skip rest of this plane
}
}
}
}
return -1, -1, -1
}
Exercise 6 🟡 — Worker Pool Quit With Multiple Reasons¶
Problem:
func runWorker(ctx context.Context, jobs <-chan Job) {
for {
select {
case <-ctx.Done():
return
case j, ok := <-jobs:
if !ok {
return
}
if err := handle(j); err != nil {
return
}
}
}
}
Question: This uses return consistently. When would a label be better?
Solution
**Discussion**: `return` is fine here because the function body ends with the loop. If post-loop cleanup is needed, a label may be cleaner: The label keeps the post-loop work outside the loop body. With `return`, you would need a `defer flushMetrics()` to achieve the same. **Benchmark**: identical perf. The choice is structural. **Key insight**: `return` exits the function immediately; `break Loop` continues post-loop work.Exercise 7 🟡 — Avoiding goto In Favor Of Labels¶
Problem:
func compute(xs []int, ys []int) int {
var total int
for _, x := range xs {
for _, y := range ys {
if x+y == 0 {
total = -1
goto Done
}
total += x * y
}
}
Done:
return total
}
Question: Can this be written without goto?
Solution
**Optimization**: Or extract: (The double pass is slightly slower; the labelled-break version is the best of both.) **Benchmark**: labelled version equals goto version; both faster than the double-pass extracted version. **Key insight**: Labelled break replaces forward `goto` for "exit nested loop on condition" patterns.Exercise 8 🔴 — Hot Loop: Label Cost¶
Problem:
func sum(xs []int) int {
total := 0
Outer:
for i, x := range xs {
if x == 0 {
break Outer
}
total += x
_ = i
}
return total
}
Question: Does the label add cost compared to the unlabelled version?
Solution
**Discussion**: The label declaration itself is free. The `break Outer` and `break` produce the same control-flow edge — the same `JMP` in the generated assembly. Cost is identical. Verify: You will find a `JMP` to the same target in both versions. **Optimization**: There is none — the code is already optimal. The only consideration is style. **Benchmark** (1M elements, no zero): - Labelled: ~600 us/op - Unlabelled: ~600 us/op Indistinguishable. **Key insight**: Labels are zero-cost. Use them where they aid clarity.Exercise 9 🔴 — Rare Case: Label-Based Early Out Beats Nested Flag Reads¶
Problem:
func searchAll(grids [][][]int, target int) (int, int, int) {
var foundG, foundI, foundJ int
found := false
for g, grid := range grids {
for i, row := range grid {
for j, v := range row {
if v == target {
foundG, foundI, foundJ = g, i, j
found = true
break
}
}
if found {
break
}
}
if found {
break
}
}
if !found {
return -1, -1, -1
}
return foundG, foundI, foundJ
}
Question: Three nested loops, three flag checks. Is the label version measurably faster?
Solution
**Optimization**:func searchAll(grids [][][]int, target int) (int, int, int) {
Search:
for g, grid := range grids {
for i, row := range grid {
for j, v := range row {
if v == target {
return g, i, j
}
_ = j
}
_ = i
}
_ = g
}
_ = Search
return -1, -1, -1
}
Exercise 10 🔴 — Benchmark Equivalence¶
Problem: Prove with a benchmark that labelled and unlabelled break produce identical performance.
Solution
package main
import "testing"
func breakLabelled(xs []int, target int) int {
Loop:
for i, x := range xs {
if x == target {
return i
}
_ = Loop
if x < 0 {
break Loop
}
}
return -1
}
func breakUnlabelled(xs []int, target int) int {
for i, x := range xs {
if x == target {
return i
}
if x < 0 {
break
}
}
return -1
}
func BenchmarkLabelled(b *testing.B) {
xs := make([]int, 1<<16)
for i := range xs {
xs[i] = i + 1
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = breakLabelled(xs, 0)
}
}
func BenchmarkUnlabelled(b *testing.B) {
xs := make([]int, 1<<16)
for i := range xs {
xs[i] = i + 1
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = breakUnlabelled(xs, 0)
}
}
Bonus Exercise 🔴 — Refactor a Real Production Pattern¶
Problem: A large service has dozens of for { select { ... } } loops with a done := false; for !done { ... if cond { done = true; break } } pattern. The codebase predates Go 1.22 conventions and the original author avoided labels.
Task: Plan a migration that introduces labelled break consistently.