Labels

Learn how Go labels work, their function-wide scope, and how to use them with break, continue, and goto to control nested loops and complex flow.

Labels give a name to a statement so that break, continue, or goto can refer to that exact location. They are the mechanism Go provides for directing control flow across nested blocks—something a plain break or continue cannot do because those only affect the innermost loop or switch.

What Labels Are in Go

A label is an identifier followed by a colon, placed immediately before a statement:

OuterLoop:
    for i := 0; i < 3; i++ {
        // ...
    }

Here OuterLoop is a label attached to the for statement. The label itself does not change how the code runs; it simply creates a named anchor that break, continue, and goto can target.

Labels live in a separate namespace from variables, types, and functions. You can have a variable named OuterLoop and a label named OuterLoop in the same function without a collision—but doing so hurts readability, so it is never recommended in practice.

Why Labels Exist

Go’s break and continue without a label only affect the innermost enclosing for, switch, or select. When you are inside a deeply nested loop and need to stop the outer loop entirely, or advance to the next iteration of an outer loop, you need a label to name that outer construct.

Without labels, you would be forced to use Boolean flags or refactor into functions with early returns—both can work, but labels often express the intent more directly and keep the loop logic in one place.

Label Scope Rules

The scope of a label is the entire function body that contains it, excluding the bodies of any nested functions. This is broader than block scope: you can declare a label near the bottom of a function and still refer to it with a goto or break from the top.

Labels and Enclosing Statements:

Although a label is visible from anywhere in the function, break and continue can only target labels that are on an enclosing for, switch, or select statement. You cannot break to a label that is on a plain block or any statement that is not a loop/switch/select. The compiler enforces this at build time.

Labels are case-sensitive, must be valid Go identifiers, and must appear before a statement—they cannot stand on their own without a statement following them.

Using Labels with break

The most common real-world use of a label is to break out of an outer loop from inside a nested loop.

package main
import "fmt"
func main() {
    found := false
    matrix := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }
Search:
    for i, row := range matrix {
        for j, val := range row {
            if val == 5 {
                fmt.Printf("Found %d at (%d,%d)\n", val, i, j)
                found = true
                break Search
            }
        }
    }
    if !found {
        fmt.Println("Not found")
    }
}

break Search exits both loops immediately and control moves to the line after the outer for. Without the label, the plain break inside the inner loop would only exit the inner loop, and the outer loop would continue to the next row.

A Common and Safe Pattern:

When you see a labeled break used to terminate a nested search, the code is usually easier to follow than alternatives that introduce extra boolean flags. The label name itself documents what is being exited.

Using Labels with continue

A labeled continue works similarly: it skips the rest of the current iteration of the named outer loop and proceeds with its next iteration.

package main
import "fmt"
func main() {
Outer:
    for i := 1; i <= 3; i++ {
        for j := 1; j <= 3; j++ {
            if i == 2 && j == 2 {
                fmt.Printf("Skipping inner loop when i=%d, j=%d\n", i, j)
                continue Outer
            }
            fmt.Printf("i=%d, j=%d\n", i, j)
        }
    }
}

When i == 2 && j == 2, the continue Outer statement jumps back to the top of the outer loop for the next value of i, bypassing any remaining work in both the inner loop and the rest of the outer loop’s body for that iteration.

Labeled continue Must Target a Loop:

You can only use continue with a label that is on an enclosing for statement. Using it with a label on a switch or select is a compile-time error because continue is only meaningful inside loops.

Labels and goto

The goto statement jumps to a labeled statement anywhere in the same function, provided the jump obeys the language’s safety restrictions. The most important restriction is that you cannot jump over a variable declaration.

package main
import "fmt"
func main() {
    n := 0
    if n == 0 {
        goto HandleZero
    }
    fmt.Println("Processing", n)
    return
HandleZero:
    fmt.Println("Input was zero; aborting")
}

This is a straightforward goto that skips the normal processing path.

Cannot Jump Over Variable Declarations:

Go prohibits goto from skipping over the creation of any variable that comes into scope after the goto target but before the goto itself. For example, goto label that leaps past x := 10 inside the same block will not compile. This prevents accessing uninitialized memory and keeps the language memory-safe.

While goto can use any label in the function, well-structured Go programs rarely need goto. When a label is used with goto, it is often for centralized error cleanup in low-level resource management—though defer is the idiomatic choice in nearly all modern Go code.

Common Mistakes and Misconceptions

  • Thinking labels have block scope. Labels are visible to the entire function. A label inside a deeply nested block can still be referenced from outside that block with goto—but break and continue restrict usage to enclosing loops/switches, not just any block.
  • Believing an unused label is an error. Unlike unused variables, an unused label does not cause a compilation error in Go. However, leaving unused labels in code is a maintenance smell and should be cleaned up.
  • Confusing break label with goto label. break and continue keep the structured control flow of loops; they do not transfer control to arbitrary points. goto can, but it must obey declaration rules. Overusing goto makes programs hard to reason about.
  • Labeling the wrong statement. If you label a plain block instead of the for statement, you cannot use break or continue with that label. The label must be directly on the loop or switch statement you intend to control.

Forgetting That Switch Is a Break Target:

A break without a label inside a switch exits the switch, not the enclosing loop. If you want to break the outer loop from inside a switch that itself is inside the loop, you must use a labeled break on the loop.

Practical Usage Patterns

Labels appear most frequently in two scenarios:

  1. Search-and-exit patterns — searching a multi‑dimensional data structure and wanting to stop all looping once a match is found.
  2. Validating complex input — iterating over nested data where a failure in any element should skip to the next outer element, not just the next inner one.

In production Go codebases, you will occasionally see labeled breaks in parsers, grid‑walking algorithms, and event‑processing loops. Because the label makes the exit intention explicit, these usages are generally accepted as readable.

When a labeled continue is used, the name of the label should clearly describe the iteration being restarted. Avoid vague names like L1 or L2; prefer NextRow or NextFile.

Summary

Labels in Go are not a separate statement—they are a notation that enhances break, continue, and goto to cross nested boundaries. Their most impactful property is function‑wide scope, which gives you the freedom to name a loop once and reference it from anywhere inside the function, subject to structural rules.

If you take away one insight, it is this: labels exist so that nested loops and switches don’t trap you. When the inner loop needs to tell the outer loop “stop” or “skip to the next round,” a label is the direct, compiler‑checked way to say it.