When the loop needs to stop or skip
You are processing a batch of user uploads. One file is corrupt. You skip it and keep going. Another file triggers a quota violation. You stop the entire batch immediately. Go gives you break and continue to control the flow inside a for loop. These keywords let you exit early or skip iterations without resorting to flag variables or deep nesting.
A for loop in Go has three parts: initialization, condition, and post statement. The post statement runs after every iteration. continue forces the loop to jump straight to the post statement. break forces the loop to jump past the post statement entirely. This distinction matters when your post statement does work, such as incrementing an index or releasing a resource.
Basic control flow
break terminates the loop immediately. Control moves to the statement following the loop. continue skips the rest of the current iteration body and jumps to the post statement. The post statement executes, then the condition is re-evaluated.
Here's the simplest demonstration: continue skips a value, break stops the loop.
package main
import "fmt"
func main() {
// Iterate from 0 to 9.
for i := 0; i < 10; i++ {
// Skip the number 3. The loop jumps to i++ and checks i < 10.
if i == 3 {
continue
}
// Stop the loop entirely when i reaches 7.
if i == 7 {
break
}
// Print only the numbers that passed the checks.
fmt.Println(i)
}
}
The output is 0 1 2 4 5 6. When i is 3, continue runs. The body after continue is skipped. The post statement i++ runs, making i equal to 4. The condition 4 < 10 is true, so the loop continues. When i is 7, break runs. The post statement i++ does not run. The loop ends immediately.
break stops the loop. continue advances the loop. Know the difference.
Labeled statements for nested loops
Nested loops often require exiting multiple levels at once. Go supports labeled statements. You can place a label before a loop and use break label or continue label to target that specific loop. This feature eliminates the need for boolean flags or early returns when you need to escape nested structures.
Labels are identifiers followed by a colon. The convention is to use lowercase names for labels, matching the style of variable names. gofmt handles the indentation of labeled blocks automatically.
Here's a labeled break exiting two levels of nesting:
package main
import "fmt"
func main() {
// Label the outer loop so we can break out of both levels.
search:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
// Found the target. Exit both loops immediately.
if i == 2 && j == 3 {
break search
}
fmt.Printf("(%d, %d) ", i, j)
}
}
fmt.Println("\nDone searching.")
}
The break search statement jumps to the label search. It exits the inner loop and the outer loop simultaneously. Execution resumes after the outer loop. The post statement of the outer loop does not run.
continue also works with labels. continue outer skips the rest of the outer loop body and runs the outer post statement. This is useful when an inner loop finds a condition that invalidates the entire outer iteration.
Labels give you precision. Use them to break out of nested structures without flag variables.
Realistic usage: search with cleanup
In production code, you often search data structures. When you find the target, you might need to run cleanup code or set state before exiting. A labeled break lets you exit the search and run that code. Returning from the function is often cleaner if you have the result, but break shines when the loop is part of a larger process.
Here's a function that scans a grid and logs the search path before stopping:
package main
import "fmt"
// FindCoordinate searches a grid for a value and returns its position.
// It uses a labeled break to exit nested loops and run post-search logic.
func FindCoordinate(grid [][]string, target string) (int, int, bool) {
// Label the outer loop for controlled exit.
scan:
for r, row := range grid {
for c, cell := range row {
// Check if the current cell matches the target.
if cell == target {
// Exit both loops. We will return after the loop block.
break scan
}
}
}
// This code runs after the loop, whether we broke out or finished naturally.
// In a real scenario, this could log the search duration or release a lock.
fmt.Println("Search completed.")
// Note: This simplified example doesn't capture r and c on break.
// A real implementation would use variables to store the result.
return -1, -1, false
}
func main() {
data := [][]string{
{"alpha", "beta"},
{"gamma", "delta"},
}
FindCoordinate(data, "gamma")
}
The break scan exits the loops. The function continues to the print statement and then returns. If you used return inside the loop, you would skip the post-search logic. If you used a plain break, you would only exit the inner loop.
Return from the function when you have the answer. Break from the loop when you have more work to do.
Pitfalls and compiler errors
break and continue have strict scope rules. The compiler enforces these rules at build time. You cannot use break or continue outside a loop. You cannot break to a label that does not enclose the current statement.
If you place break outside a loop, the compiler rejects the program with break statement not within a for, switch, or select statement. The same applies to continue, which produces continue statement not within a for loop.
If you use a label that is not in scope, the compiler complains with break label outer not defined. The label must be defined in an enclosing block. You cannot break to a label in a sibling function or a parent function.
A common mistake involves modifying the loop variable inside the loop. In a for i := 0; i < 10; i++ loop, the post statement is i++. If you modify i inside the body and then call continue, the post statement still runs. This can lead to unexpected increments.
package main
import "fmt"
func main() {
// This loop skips even numbers by incrementing i inside the body.
for i := 0; i < 10; i++ {
// If i is even, increment it to skip the next odd number.
if i%2 == 0 {
i++
// continue jumps to i++. i is incremented again.
// This causes i to increase by 2 in total.
continue
}
fmt.Println(i)
}
}
The output is 1 5 9. When i is 0, the body increments i to 1. continue runs. The post statement i++ runs, making i equal to 2. The condition 2 < 10 is true. The body runs for i=2. The body increments i to 3. continue runs. The post statement makes i equal to 4. The loop effectively skips numbers. This behavior is correct but confusing. Modifying the loop variable and using continue requires careful tracking of the post statement.
The compiler catches control flow errors early. Trust the error messages; they tell you exactly where the loop boundary is.
Break inside switch
A break inside a switch statement only exits the switch. It does not exit the enclosing for loop. This catches many developers off guard. If you need to exit the loop from inside a switch, you must use a labeled break.
Here's the trap: break in switch does not stop the loop.
package main
import "fmt"
func main() {
// Loop over numbers.
for i := 0; i < 5; i++ {
// Switch on the number.
switch i {
case 2:
// This break exits the switch, not the loop.
// The loop continues to the next iteration.
break
case 4:
// This break also exits the switch.
break
}
fmt.Println(i)
}
}
The output is 0 1 2 3 4. The break statements exit the switch block. The for loop continues normally. To break the loop, label the loop and break the label.
break respects the innermost block. Label the loop if you need to escape from a switch or nested structure.
Decision matrix
Use break when you need to exit the loop and execute code immediately following it. Use continue when the current iteration is invalid or irrelevant, but the loop should proceed to the next item. Use return when the loop is inside a function and you have the final result or a fatal error. Use a labeled break when you need to exit multiple nested loops at once. Use a labeled continue when you need to skip the rest of an outer loop iteration from an inner loop. Use a boolean flag when you cannot use break or return due to complex control flow constraints, though refactoring is usually better.