Navigating Loops in Go: The Role of Labeled continue Statements

Table of Contents

Reflecting on my computer engineering studies, I recall my professor’s cautionary words about the goto statement.

He emphasized that its misuse in early programming led to convoluted, hard-to-maintain code—often referred to as “spaghetti code”.

This widespread abuse prompted many languages to eliminate goto in favor of more structured control flow constructs.

The seminal 1968 letter by Edsger W. Dijkstra, titled “Go To Statement Considered Harmful” played a pivotal role in this shift.

Dijkstra argued that indiscriminate use of goto complicates program verification and understanding, advocating for structured programming practices instead.

If you are wondering if this is Dijkstra behind the famous shortest path algorithm, you are correct.

However, the Go programming language includes the goto statement, although not advocating the usage of it, but also maintains a really cool feature called labeled continue statements.

Traditional nested loops

Recently I was solving an Advent of code problem and I came across a situation where I had to use a nested loop.

Navigating nested loops in Go (or any programming language) can be challenging, especially when you need to control the flow from an inner loop to an outer one.

Let’s see an example solution below following the traditional approach, some parts are removed for brevity:

func Part1(data [][]string, ruleMap map[string][]int) (sum int) {
	for _, update := range data {
		isValid := true

		// Check validity of the entire update
		for _, u := range update {
			// Check if rule exists
			if ruleMap[u] == 0 {
				isValid = false
				break // Exit the innermost loop early
			}

			// If we've already found it's invalid, no need to continue checking
			if !isValid {
				break
			}
		}

		// Only add to sum if the update was valid
		if isValid {
			sum += update[len(update)/2]
		}
	}

	return sum
}

In this example, a boolean flag isValid is used to indicate whether an update was valid in the inner loop. If the update is invalid, the inner loop breaks, and the outer loop checks the isValid flag to decide whether to continue. While functional, this approach introduces additional state management, which can complicate the code and make it harder to maintain.

Using Labeled continue

Go’s labeled continue statements provide a more elegant, or I would argue in this case readable solution:

func Part1(data string[][], ruleMap map[string][]int) (sum int) {

	OUTER:
	for _, update := range data {
		for _, u := range update {
			// Check if rule exists
			if ruleMap[u] == 0 {
				continue OUTER
			}
		}

		sum += update[len(update)/2]
	}

	return sum
}

Here, OUTER is a label assigned to the outer loop. When continue OUTER is executed inside the inner loop, it immediately continues the next iteration of the outer loop, effectively skipping any code that follows in both the inner and outer loops for the current iteration. This approach eliminates the need for the isValid flag, resulting in cleaner and more readable code.

Conclusion

Utilizing labeled continue statements in Go can significantly improve the readability and maintainability of code involving nested loops. By directly controlling loop flow, you can eliminate unnecessary state variables and make your code more intuitive.

However, it’s essential to use this feature judiciously to maintain code simplicity and avoid potential confusion. Else your code will be labeled as spaghetti code, and you don’t want that, do you?


comments powered by Disqus