break Statement — Specification¶
Source: Go Language Specification — §Break_statements
1. Spec Reference¶
The break statement is defined in the Go Language Specification at:
https://go.dev/ref/spec#Break_statements
| Field | Value |
|---|---|
| Official Spec | https://go.dev/ref/spec |
| Primary Section | §Break_statements |
| Related Sections | §For_statements, §Switch_statements, §Select_statements, §Labeled_statements |
| Go Version | Go 1.0+ (unchanged since initial release) |
Official spec text:
"A 'break' statement terminates execution of the innermost 'for', 'switch', or 'select' statement within the same function."
And for the labeled form:
"If there is a label, it must be that of an enclosing 'for', 'switch', or 'select' statement, and that is the one whose execution terminates."
Key distinction from C/C++/Java:
In Go,
breakin aswitchcase terminates the switch statement. There is no fallthrough by default, andbreakdoes not mean "stop falling through" as it does in C. Instead, Go has an explicitfallthroughkeyword.
2. Formal Grammar (EBNF)¶
From the Go Language Specification:
Valid syntactic forms¶
break // without label — terminates innermost for, switch, or select
break OuterLoop // with label — terminates the for/switch/select labeled OuterLoop
Grammar breakdown¶
| Component | Description |
|---|---|
break | The keyword itself — always required |
Label | Optional — an identifier referencing a labeled enclosing statement |
identifier | A sequence of letters (including _) and digits, starting with a letter |
The label, if present, must refer to a LabeledStmt where the Statement is a for, switch, or select statement. The label must be in the same function as the break.
3. Core Rules & Constraints¶
Rule 1: break terminates the innermost for, switch, or select¶
Without a label, break terminates the execution of the innermost enclosing for, switch, or select statement. Control passes to the statement immediately following the terminated construct.
// break terminates the for loop
for i := 0; i < 10; i++ {
if i == 5 {
break // exits the for loop
}
}
// execution continues here after break
Rule 2: break with label terminates the labeled statement¶
When a label is provided, break terminates the for, switch, or select statement that the label identifies, regardless of nesting depth.
Outer:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if i+j == 5 {
break Outer // exits BOTH loops
}
}
}
// execution continues here
Rule 3: Label must refer to an enclosing for, switch, or select¶
The label used with break must satisfy all of the following: - It must be declared in the same function. - It must be attached to a for, switch, or select statement. - That statement must enclose the break statement.
// INVALID — label on an if statement
MyIf:
if true {
break MyIf // compile error: invalid break label MyIf
}
// INVALID — label on a block
MyBlock:
{
break MyBlock // compile error: invalid break label MyBlock
}
Rule 4: break cannot cross function boundaries¶
A break statement cannot break out of a function boundary. The enclosing for, switch, or select must be in the same function as the break.
func inner() {
break // compile error: break is not in a loop, switch, or select
}
func outer() {
for i := 0; i < 10; i++ {
inner() // inner's break does NOT affect outer's for loop
}
}
This includes closures and anonymous functions — a break inside a closure does not affect loops outside that closure:
for i := 0; i < 10; i++ {
func() {
break // compile error: break is not in a loop, switch, or select
}()
}
Rule 5: break in switch does NOT cause fallthrough (unlike C)¶
In C, break inside a switch case prevents fallthrough to the next case. In Go, there is no implicit fallthrough — each case body terminates automatically. Using break inside a switch case simply exits the switch statement early (before the end of the case body).
switch x {
case 1:
fmt.Println("one")
// no break needed — Go does not fall through
case 2:
fmt.Println("two")
break // legal but redundant — same effect as reaching end of case
case 3:
fmt.Println("three")
}
Rule 6: break outside for/switch/select is a compile-time error¶
Rule 7: Unreachable code after break¶
Code after a break statement within the same block is unreachable. The Go compiler does not produce a compile error for unreachable code after break (unlike after return), but the code will never execute.
4. Type Rules¶
break is a statement, not an expression¶
break produces no value and has no type. It cannot be used in an expression context:
Label scoping rules¶
Labels in Go follow specific scoping rules:
- Labels have their own namespace — a label name does not conflict with variable, function, type, or package names.
x := 10
x: // legal — label "x" does not conflict with variable "x"
for i := 0; i < 5; i++ {
break x // breaks the labeled for loop
}
- Labels are function-scoped — a label is visible throughout the entire function body, regardless of where it is declared. Two labels in the same function cannot share the same name.
func f() {
Loop:
for {
break Loop
}
Loop: // compile error: label Loop already defined
for {
break Loop
}
}
- Different functions may use the same label name — labels do not conflict across function boundaries.
func f() {
Loop:
for {
break Loop
}
}
func g() {
Loop: // perfectly legal — different function
for {
break Loop
}
}
- Unused labels are compile-time errors — every declared label must be referenced by a
goto,break, orcontinue.
Label identifier rules¶
Labels follow Go's standard identifier rules: - Must start with a Unicode letter or underscore (_). - May contain Unicode letters, digits, and underscores. - Convention: PascalCase (e.g., OuterLoop, RetryLoop) or ALL_CAPS (e.g., OUTER).
5. Behavioral Specification¶
Normal break (no label)¶
When break executes without a label, it terminates the innermost enclosing for, switch, or select statement. The term "innermost" means the closest enclosing construct of those three types in the lexical structure.
for i := 0; i < N; i++ { // <-- this is the innermost for
if condition {
break // terminates this for loop
}
}
// control resumes here
What "innermost" means with nested constructs¶
The "innermost" rule applies to the nearest enclosing for, switch, or select. This creates important distinctions when these constructs are nested:
Scenario 1: switch inside for
for i := 0; i < 10; i++ {
switch i % 3 {
case 0:
break // breaks the SWITCH, not the for loop
}
fmt.Println(i) // this STILL executes for case 0
}
Here, break terminates the switch (the innermost enclosing breakable statement), not the for loop. This is a common source of bugs.
Scenario 2: select inside for
for {
select {
case msg := <-ch:
if msg == "quit" {
break // breaks the SELECT, not the for loop
}
process(msg)
}
// this STILL executes after break
}
Again, break only exits the select, not the for loop.
Scenario 3: for inside switch
switch mode {
case "fast":
for i := 0; i < 100; i++ {
if done(i) {
break // breaks the FOR loop, not the switch
}
}
fmt.Println("after inner for") // this executes after break
}
Labeled break — bypassing the innermost rule¶
The labeled form of break lets you specify exactly which enclosing construct to terminate:
Loop:
for i := 0; i < 10; i++ {
switch i % 3 {
case 0:
break Loop // breaks the FOR loop (labeled), not the switch
}
fmt.Println(i) // NOT reached when i%3 == 0
}
Compile-time label validation¶
The Go compiler performs static validation of all labels at compile time:
- Existence check — the label must be declared.
- Enclosure check — the labeled statement must enclose the
break. - Statement type check — the labeled statement must be
for,switch, orselect. - Function boundary check — the label must be in the same function.
- Usage check — the label must be referenced somewhere.
All of these produce compile-time errors, never runtime errors. There is no runtime cost to labeled breaks.
Interaction with defer¶
break does not trigger deferred functions. Deferred functions only execute when their enclosing function returns, not when a loop, switch, or select is exited.
func f() {
defer fmt.Println("function deferred") // runs at function return
for i := 0; i < 5; i++ {
defer fmt.Println("loop deferred", i) // accumulates, runs at function return
if i == 2 {
break // does NOT trigger any deferred calls
}
}
fmt.Println("after loop")
}
// Output:
// after loop
// loop deferred 2
// loop deferred 1
// loop deferred 0
// function deferred
Interaction with goroutines¶
break cannot cross goroutine boundaries. A break inside a goroutine's function only affects constructs within that function:
Loop:
for i := 0; i < 10; i++ {
go func() {
break Loop // compile error: break is not in a loop, switch, or select
// (also: label Loop is in a different function)
}()
}
6. Defined vs Undefined Behavior¶
Go's specification fully defines all behaviors related to break. There is no undefined behavior — violations are caught at compile time.
| Situation | Behavior |
|---|---|
break inside for | Defined — terminates the for loop |
break inside switch | Defined — terminates the switch statement |
break inside select | Defined — terminates the select statement |
break Label where label is on enclosing for | Defined — terminates the labeled for |
break Label where label is on enclosing switch | Defined — terminates the labeled switch |
break Label where label is on enclosing select | Defined — terminates the labeled select |
break outside for/switch/select | Compile-time error: "break is not in a loop, switch, or select" |
break Label with non-existent label | Compile-time error: "undefined label Label" |
break Label where label is on if/block/goto | Compile-time error: "invalid break label Label" |
break Label where label is not enclosing | Compile-time error: "invalid break label Label" |
break Label across function boundary | Compile-time error |
break inside closure affecting outer loop | Compile-time error |
| Label declared but never referenced | Compile-time error: "label Label defined and not used" |
Deferred functions on break | Defined — defers are NOT triggered (only on function return) |
Code after break in same block | Defined — unreachable but compiles without error |
7. Edge Cases from Spec¶
Edge Case 1: break in switch nested inside for (the most common pitfall)¶
This is the single most common mistake with break in Go. Developers coming from other languages expect break to exit the for loop, but it only exits the switch.
package main
import "fmt"
func main() {
// BUG: break exits the switch, not the for loop
for i := 0; i < 5; i++ {
switch i {
case 3:
fmt.Println("found 3, trying to exit loop...")
break // only exits the switch!
}
fmt.Println("still in loop, i =", i) // this prints for ALL values including 3
}
fmt.Println("---")
// FIX: use labeled break to exit the for loop
Loop:
for i := 0; i < 5; i++ {
switch i {
case 3:
fmt.Println("found 3, exiting loop...")
break Loop // exits the for loop
}
fmt.Println("still in loop, i =", i) // NOT reached when i == 3
}
}
// Output:
// found 3, trying to exit loop...
// still in loop, i = 0
// still in loop, i = 1
// still in loop, i = 2
// still in loop, i = 3
// still in loop, i = 4
// ---
// still in loop, i = 0
// still in loop, i = 1
// still in loop, i = 2
// found 3, exiting loop...
Edge Case 2: Labeled break from deeply nested inner loop¶
package main
import "fmt"
func main() {
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 0, 9}, // contains zero
{10, 11, 12},
}
found := false
var foundRow, foundCol int
Search:
for i, row := range matrix {
for j, val := range row {
if val == 0 {
found = true
foundRow, foundCol = i, j
break Search // exits BOTH loops immediately
}
}
}
if found {
fmt.Printf("Zero found at [%d][%d]\n", foundRow, foundCol)
} else {
fmt.Println("Zero not found")
}
}
// Output: Zero found at [2][1]
Edge Case 3: break in select case (event loop pattern)¶
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string, 3)
ch <- "hello"
ch <- "world"
ch <- "done"
timeout := time.After(1 * time.Second)
// break inside select only exits the select, NOT the for loop
for {
select {
case msg := <-ch:
if msg == "done" {
fmt.Println("Received done signal")
break // only exits the select — loop continues!
}
fmt.Println("Received:", msg)
case <-timeout:
fmt.Println("Timeout")
break // only exits the select — loop continues!
}
fmt.Println(" (still in for loop)") // always reached after select
// Without a way to exit the for loop, this runs forever
// after all channel messages and timeout are consumed.
break // explicit break to exit the for loop for this demo
}
}
The correct pattern uses a labeled break:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string, 3)
ch <- "hello"
ch <- "world"
ch <- "done"
timeout := time.After(1 * time.Second)
Loop:
for {
select {
case msg := <-ch:
if msg == "done" {
fmt.Println("Received done signal, exiting loop")
break Loop // exits the FOR loop
}
fmt.Println("Received:", msg)
case <-timeout:
fmt.Println("Timeout, exiting loop")
break Loop // exits the FOR loop
}
}
fmt.Println("Loop exited cleanly")
}
// Output:
// Received: hello
// Received: world
// Received done signal, exiting loop
// Loop exited cleanly
Edge Case 4: break vs return in goroutines¶
break is purely a control flow statement for loops/switches/selects. It cannot be used to exit goroutines. To stop a goroutine, use return, context cancellation, or channel signaling.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; ; i++ {
select {
case <-done:
fmt.Println("goroutine: received done signal")
return // use return, not break, to exit the goroutine
default:
if i >= 5 {
close(done)
return // return exits the goroutine
}
fmt.Println("goroutine: working", i)
}
}
}()
wg.Wait()
fmt.Println("main: goroutine finished")
}
Edge Case 5: break in type switch¶
break works the same way in type switches as in expression switches:
package main
import "fmt"
func classify(values []interface{}) {
for _, v := range values {
switch v.(type) {
case int:
fmt.Printf("%v is int\n", v)
break // exits the type switch (redundant, but legal)
case string:
fmt.Printf("%v is string\n", v)
case nil:
fmt.Println("nil value encountered")
break // exits the type switch (redundant)
default:
fmt.Printf("%v is unknown type\n", v)
}
}
}
func main() {
classify([]interface{}{42, "hello", nil, 3.14})
}
// Output:
// 42 is int
// hello is string
// nil value encountered
// 3.14 is unknown type
Edge Case 6: Labeled break on switch (less common, but valid)¶
You can label a switch statement and use break Label to exit it. This is useful when the switch is nested inside another switch:
package main
import "fmt"
func process(action string, mode int) {
OuterSwitch:
switch action {
case "run":
switch mode {
case 1:
fmt.Println("run mode 1")
case 2:
fmt.Println("run mode 2 — aborting action")
break OuterSwitch // exits the OUTER switch
case 3:
fmt.Println("run mode 3")
}
fmt.Println("after inner switch") // NOT reached when mode == 2
case "stop":
fmt.Println("stopping")
}
fmt.Println("after outer switch") // always reached
}
func main() {
process("run", 1)
fmt.Println("---")
process("run", 2)
fmt.Println("---")
process("run", 3)
}
// Output:
// run mode 1
// after inner switch
// after outer switch
// ---
// run mode 2 — aborting action
// after outer switch
// ---
// run mode 3
// after inner switch
// after outer switch
Edge Case 7: Labeled break on select (less common, but valid)¶
package main
import "fmt"
func main() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch1 <- 42
Sel:
select {
case v := <-ch1:
if v > 40 {
fmt.Println("got high value, breaking select")
break Sel // explicit labeled break on the select
}
fmt.Println("got:", v)
case v := <-ch2:
fmt.Println("from ch2:", v)
}
fmt.Println("after select")
}
// Output:
// got high value, breaking select
// after select
Edge Case 8: Triple-nested loop with labeled break¶
package main
import "fmt"
func main() {
// Find the first triple (x, y, z) where x*y*z == 60
Outer:
for x := 1; x <= 10; x++ {
for y := 1; y <= 10; y++ {
for z := 1; z <= 10; z++ {
if x*y*z == 60 {
fmt.Printf("Found: %d * %d * %d = 60\n", x, y, z)
break Outer // exits all three loops
}
}
}
}
fmt.Println("Search complete")
}
// Output:
// Found: 1 * 6 * 10 = 60
// Search complete
Edge Case 9: break with for-range over string (runes)¶
package main
import "fmt"
func main() {
text := "Hello, World!"
// Find the first comma
for i, r := range text {
if r == ',' {
fmt.Printf("Comma found at byte index %d\n", i)
break // exits the for-range loop
}
fmt.Printf("%c ", r)
}
fmt.Println()
}
// Output:
// H e l l o
// Comma found at byte index 5
8. Version History¶
| Version | Change |
|---|---|
| Go 1.0 (2012) | break statement introduced with full semantics: unlabeled and labeled forms, applicable to for, switch, and select |
| Go 1.0+ | No changes to break semantics in any subsequent release |
The break statement has been completely stable since Go 1.0. The Go1 compatibility guarantee ensures that its behavior will not change in any future Go 1.x release.
Notable design decisions made before Go 1.0: - No implicit fallthrough in switch — Go deliberately diverged from C's switch semantics, making break inside switch less essential but still valid. - Labeled break applies to for/switch/select — unlike some languages where labeled break only applies to loops, Go extends it to switch and select as well. - No multi-level numeric break — Go does not support break 2 or break N syntax found in some languages (e.g., PHP). Labels are the mechanism for multi-level breaks.
9. Implementation-Specific Behavior¶
Jump instruction generation¶
The break statement compiles to an unconditional jump instruction (JMP on x86/AMD64, B on ARM64) that targets the instruction immediately following the terminated construct.
For labeled breaks, the compiler resolves the target label at compile time. There is no runtime overhead compared to an unlabeled break — both result in a single jump instruction.
SSA (Static Single Assignment) form¶
In the Go compiler's SSA intermediate representation, break creates a basic block boundary. The current basic block terminates with an unconditional branch to the block representing the code after the loop/switch/select.
Stack and registers¶
break does not affect the call stack. No stack frames are pushed or popped. The compiler may need to generate code to: - Restore registers that held loop variables (for optimized builds where loop variables are in registers). - No cleanup of local variables is needed — Go's garbage collector handles memory.
Comparison with return¶
Unlike return, break does not: - Trigger deferred function calls. - Copy return values to the caller's stack frame. - Execute the function epilogue.
break is purely an intra-function, intra-statement control transfer.
10. Spec Compliance Checklist¶
-
breakis only used insidefor,switch, orselectstatements - When using
breakinside aswitchnested in afor, the intention is verified — did you mean to exit the switch or the for loop? - When using
breakinside aselectnested in afor, the intention is verified — did you mean to exit the select or the for loop? - Labeled
breakuses a label that is on an enclosingfor,switch, orselect(notif, block, or other statements) - All declared labels are referenced (no unused label compile errors)
- Labels do not attempt to cross function boundaries (including closures and goroutines)
- Deferred functions are not expected to run on
break(only on function return) - Labels follow Go naming conventions (PascalCase:
OuterLoop,Retry,Search) - Labeled breaks in complex nested structures are documented with comments for clarity
-
breakis not confused withreturnwhen the goal is to exit a goroutine - The distinction between
break(exits switch) and no-op end-of-case (also exits switch) is understood
11. Official Examples¶
Example 1: Simple break in a for loop¶
package main
import "fmt"
func main() {
// Find the first number divisible by 7 in range [1, 100]
for i := 1; i <= 100; i++ {
if i%7 == 0 {
fmt.Println("First multiple of 7:", i)
break // exit the loop immediately
}
}
// Sum numbers until the running total exceeds 50
sum := 0
for i := 1; ; i++ { // infinite loop
sum += i
if sum > 50 {
fmt.Printf("Sum exceeded 50 at i=%d, sum=%d\n", i, sum)
break // exit the infinite loop
}
}
}
// Output:
// First multiple of 7: 7
// Sum exceeded 50 at i=10, sum=55
Example 2: Labeled break to exit nested loops¶
package main
import "fmt"
func main() {
// Search for a target value in a 2D grid
grid := [][]int{
{11, 22, 33},
{44, 55, 66},
{77, 88, 99},
}
target := 55
found := false
Search:
for row := 0; row < len(grid); row++ {
for col := 0; col < len(grid[row]); col++ {
if grid[row][col] == target {
fmt.Printf("Found %d at grid[%d][%d]\n", target, row, col)
found = true
break Search // exit BOTH loops
}
}
}
if !found {
fmt.Printf("%d not found in grid\n", target)
}
}
// Output: Found 55 at grid[1][1]
Example 3: break in switch inside for — the pitfall and the fix¶
package main
import "fmt"
func main() {
commands := []string{"start", "process", "stop", "process", "end"}
// WRONG: break exits the switch, not the for loop
fmt.Println("=== Wrong (break exits switch) ===")
for _, cmd := range commands {
switch cmd {
case "stop":
fmt.Println("Stop command received!")
break // exits the switch only
default:
fmt.Println("Executing:", cmd)
}
fmt.Println(" (loop continues)") // always reached
}
// CORRECT: labeled break exits the for loop
fmt.Println("\n=== Correct (labeled break exits for) ===")
CmdLoop:
for _, cmd := range commands {
switch cmd {
case "stop":
fmt.Println("Stop command received!")
break CmdLoop // exits the for loop
default:
fmt.Println("Executing:", cmd)
}
fmt.Println(" (loop continues)") // NOT reached on "stop"
}
fmt.Println("Done")
}
// Output:
// === Wrong (break exits switch) ===
// Executing: start
// (loop continues)
// Executing: process
// (loop continues)
// Stop command received!
// (loop continues)
// Executing: process
// (loop continues)
// Executing: end
// (loop continues)
//
// === Correct (labeled break exits for) ===
// Executing: start
// (loop continues)
// Executing: process
// (loop continues)
// Stop command received!
// Done
Example 4: break in select inside for — event loop pattern¶
package main
import (
"context"
"fmt"
"time"
)
func eventLoop(ctx context.Context, events <-chan string) {
processed := 0
Loop:
for {
select {
case <-ctx.Done():
fmt.Println("Context cancelled, exiting event loop")
break Loop // exits the for loop
case event, ok := <-events:
if !ok {
fmt.Println("Event channel closed, exiting event loop")
break Loop // exits the for loop
}
fmt.Printf("Processing event: %s\n", event)
processed++
}
}
fmt.Printf("Total events processed: %d\n", processed)
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
events := make(chan string, 5)
events <- "click"
events <- "scroll"
events <- "keypress"
close(events) // close the channel to trigger the exit
eventLoop(ctx, events)
}
// Output:
// Processing event: click
// Processing event: scroll
// Processing event: keypress
// Event channel closed, exiting event loop
// Total events processed: 3
Example 5: break with for-range and early termination¶
package main
import (
"fmt"
"strings"
)
func findFirstError(logs []string) (string, bool) {
for _, line := range logs {
if strings.Contains(line, "ERROR") {
return line, true // could also use break + variable
}
}
return "", false
}
func findAllBeforeError(logs []string) []string {
var clean []string
for _, line := range logs {
if strings.Contains(line, "ERROR") {
break // stop processing at first error
}
clean = append(clean, line)
}
return clean
}
func main() {
logs := []string{
"INFO: Starting server",
"INFO: Listening on :8080",
"WARN: High memory usage",
"ERROR: Connection refused",
"INFO: Retrying",
}
if errLine, found := findFirstError(logs); found {
fmt.Println("First error:", errLine)
}
clean := findAllBeforeError(logs)
fmt.Println("Logs before first error:")
for _, line := range clean {
fmt.Println(" ", line)
}
}
// Output:
// First error: ERROR: Connection refused
// Logs before first error:
// INFO: Starting server
// INFO: Listening on :8080
// WARN: High memory usage
Example 6: Compile error examples (invalid usage)¶
// These examples demonstrate compile-time errors.
// Each function would fail to compile.
package main
// Example 1: break outside any breakable statement
func invalidBreakOutside() {
break // compile error: break is not in a loop, switch, or select
}
// Example 2: break with undefined label
func invalidUndefinedLabel() {
for i := 0; i < 10; i++ {
break NonExistent // compile error: undefined label NonExistent
}
}
// Example 3: break with label on non-breakable statement
func invalidLabelOnIf() {
MyIf:
if true {
break MyIf // compile error: invalid break label MyIf
}
}
// Example 4: break in closure cannot affect outer loop
func invalidBreakInClosure() {
for i := 0; i < 10; i++ {
func() {
break // compile error: break is not in a loop, switch, or select
}()
}
}
// Example 5: labeled break where label is not enclosing
func invalidNonEnclosingLabel() {
Other:
for j := 0; j < 5; j++ {
// Other encloses this
}
for i := 0; i < 10; i++ {
break Other // compile error: invalid break label Other
// (Other does not enclose this break)
}
}
// Example 6: unused label
func invalidUnusedLabel() {
Unused: // compile error: label Unused defined and not used
for {
break // unlabeled break does not "use" the label
}
}
Example 7: Labeled break with three levels of nesting¶
package main
import "fmt"
func main() {
type Cell struct {
layers [][]int
}
grid := [][]Cell{
{
{layers: [][]int{{1, 2}, {3, 4}}},
{layers: [][]int{{5, 6}, {7, 8}}},
},
{
{layers: [][]int{{9, 10}, {11, 12}}},
{layers: [][]int{{13, -1}, {15, 16}}}, // contains -1
},
}
// Find the first negative value in the entire 4-level structure
target := -1
foundPath := ""
Level1:
for i, row := range grid {
for j, cell := range row {
for k, layer := range cell.layers {
for l, val := range layer {
if val == target {
foundPath = fmt.Sprintf("grid[%d][%d].layers[%d][%d]", i, j, k, l)
break Level1 // exits ALL four loops
}
}
}
}
}
if foundPath != "" {
fmt.Printf("Found %d at %s\n", target, foundPath)
} else {
fmt.Println("Not found")
}
}
// Output: Found -1 at grid[1][1].layers[0][1]
12. Related Spec Sections¶
| Section | URL | Relationship to break |
|---|---|---|
| Break statements | https://go.dev/ref/spec#Break_statements | Primary definition |
| Continue statements | https://go.dev/ref/spec#Continue_statements | Sibling flow control — advances loop iteration instead of terminating |
| Goto statements | https://go.dev/ref/spec#Goto_statements | Alternative control transfer — jumps to arbitrary label |
| For statements | https://go.dev/ref/spec#For_statements | Target of break — break can terminate for loops |
| Switch statements | https://go.dev/ref/spec#Switch_statements | Target of break — break can terminate switch statements |
| Select statements | https://go.dev/ref/spec#Select_statements | Target of break — break can terminate select statements |
| Labeled statements | https://go.dev/ref/spec#Labeled_statements | Mechanism for labeled break — labels identify the target statement |
| Terminating statements | https://go.dev/ref/spec#Terminating_statements | break is NOT a terminating statement (it terminates a construct, not the function) |
| Return statements | https://go.dev/ref/spec#Return_statements | Alternative — use return to exit functions; break only exits loops/switch/select |
| Fallthrough statements | https://go.dev/ref/spec#Fallthrough_statements | Go's explicit fallthrough replaces C's implicit fallthrough, making break in switch less essential |
| Defer statements | https://go.dev/ref/spec#Defer_statements | Interaction — break does NOT trigger deferred calls |
Appendix: break vs continue vs goto vs return — Quick Comparison¶
| Statement | Exits | Triggers defer? | Can use label? | Applies to |
|---|---|---|---|---|
break | Innermost for/switch/select (or labeled one) | No | Yes — for/switch/select labels | for, switch, select |
continue | Current iteration of innermost for (or labeled one) | No | Yes — for labels only | for only |
goto | N/A — transfers to label | No | Yes — any label in function | Any point in function |
return | Entire function | Yes | No | Functions |
Appendix: Common Patterns Using break¶
Pattern 1: Search with early exit¶
func contains(slice []int, target int) bool {
for _, v := range slice {
if v == target {
return true // return is cleaner than break + flag for functions
}
}
return false
}
Pattern 2: Process until sentinel value¶
func processUntilDone(ch <-chan string) []string {
var results []string
for msg := range ch {
if msg == "DONE" {
break
}
results = append(results, msg)
}
return results
}
Pattern 3: Timeout with labeled break in select-for¶
func waitForResult(result <-chan int, timeout time.Duration) (int, bool) {
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case v := <-result:
return v, true
case <-timer.C:
return 0, false
}
}
Pattern 4: Breaking out of infinite loop on condition¶
func readLines(scanner *bufio.Scanner) []string {
var lines []string
for scanner.Scan() { // Scan returns false on EOF or error
line := scanner.Text()
if line == "" {
break // stop at first empty line
}
lines = append(lines, line)
}
return lines
}
Summary¶
The break statement in Go is a fundamental control flow mechanism with two forms:
- Unlabeled:
break— terminates the innermost enclosingfor,switch, orselect. - Labeled:
break Label— terminates thefor,switch, orselectidentified by the label.
Key points to remember: - break in a switch exits the switch, not an enclosing loop. Use break Label to exit the loop. - break in a select exits the select, not an enclosing loop. Use break Label to exit the loop. - Labels must be on for, switch, or select statements and must enclose the break. - break cannot cross function boundaries (including closures and goroutines). - break does not trigger deferred functions. - All label-related errors are caught at compile time — there are no runtime surprises.