Scala: How to use break and continue in for and while loops

This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 3.5, “ Scala: How to use break and continue in for loops (and while loops)”

Problem

You have a situation where you need to use a break or continue construct, but Scala doesn’t have break or continue keywords.

Solution

It’s true that Scala doesn’t have break and continue keywords, but it does offer similar functionality through scala.util.control.Breaks.

The following code demonstrates the Scala “break” and “continue” approach:

package com.alvinalexander.breakandcontinue

import util.control.Breaks._

object BreakAndContinueDemo extends App {

    println("\n=== BREAK EXAMPLE ===")
    breakable {
        for (i <- 1 to 10) {
            println(i)
            if (i > 4) break  // break out of the for loop
        }
    }

    println("\n=== CONTINUE EXAMPLE ===")
    val searchMe = "peter piper picked a peck of pickled peppers"
    var numPs = 0
    for (i <- 0 until searchMe.length) {
        breakable {
          if (searchMe.charAt(i) != 'p') {
              break  // break out of the 'breakable', continue the outside loop
          } else {
              numPs += 1
          }
      }
    }

    println("Found " + numPs + " p's in the string.")
}

Here’s the output from the code:

=== BREAK EXAMPLE ===
1
2
3
4
5
=== CONTINUE EXAMPLE ===
Found 9 p's in the string.

(The “pickled peppers” example comes from a continue example in the Java documentation. More on this at the end of the recipe.)

The following discussions describe how this code works.

The ‘break’ example

The break example is pretty easy to reason about. Again, here’s the code:

breakable {
    for (i <- 1 to 10) {
        println(i)
        if (i > 4) break  // break out of the for loop
    }
}

In this case, when i becomes greater than 4, the break “keyword” is reached. At this point an exception is thrown, and the for loop is exited. The breakable “keyword” essentially catches the exception, and the flow of control continues with any other code that might be after the breakable block.

Note that break and breakable aren’t actually keywords; they’re methods in scala.util.control.Breaks. In Scala 2.10, the break method is declared as follows to throw an instance of a BreakControl exception when it’s called:

private val breakException = new BreakControl
def break(): Nothing = { throw breakException }

The breakable method is defined to catch a BreakControl exception, like this:

def breakable(op: => Unit) {
    try {
        op
    } catch {
        case ex: BreakControl => if (ex ne breakException) throw ex
    }
  }

See Recipe 3.18 for examples of how to implement your own control structures in a manner similar to the Breaks library.

The ‘continue’ example

Given the explanation for the break example, you can now reason about how the “continue” example works. Here’s the code again:

val searchMe = "peter piper picked a peck of pickled peppers"
var numPs = 0

for (i <- 0 until searchMe.length) {
    breakable {
        if (searchMe.charAt(i) != 'p') {
            break  // break out of the 'breakable', continue the outside loop
        } else {
            numPs += 1
        }
    }
}
println("Found " + numPs + " p's in the string.")

Following the earlier explanation, as the code walks through the characters in the String variable named searchMe, if the current character is not the letter p, the code breaks out of the if/then statement, and the loop continues executing.

As before, what really happens is that the break method is reached, an exception is thrown, and that exception is caught by breakable. The exception serves to break out of the if/then statement, and catching it allows the for loop to continue executing with the next element.

General syntax

The general syntax for implementing break and continue functionality is shown in the following examples, which are partially written in pseudocode, and compared to their Java equivalents..

To implement a break, this Scala:

breakable {
    for (x <- xs) {
        if (cond) break
    }
}

corresponds to this Java:

for (X x : xs) {
    if (cond) break;
}

To implement continue functionality, this Scala:

for (x <- xs) {
    breakable {
        if (cond) break
    }
}

corresponds to this Java:

for (X x : xs) {
    if (cond) continue;
}

About that ‘continue’ example...

The “continue” example shown is a variation of the Java continue example shown on the Oracle website. If you know Scala, you know that there are better ways to solve this particular problem. For instance, a direct approach is to use the count method with a simple anonymous function:

val count = searchMe.count(_ == 'p')

When this code is run, count is again 9.

Nested loops and labeled breaks

In some situations, you may need nested break statements. Or, you may prefer labeled break statements. In either case, you can create labeled breaks as shown in the following example:

package com.alvinalexander.labeledbreaks

object LabeledBreakDemo extends App {
    import scala.util.control._
    val Inner = new Breaks
    val Outer = new Breaks
    Outer.breakable {
        for (i <- 1 to 5) {
            Inner.breakable {
                for (j <- 'a' to 'e') {
                    if (i == 1 && j == 'c') Inner.break else println(s"i: $i, j: $j")
                    if (i == 2 && j == 'b') Outer.break
                }
            }
        }
    }
}

In this example, if the first if condition is met, an exception is thrown and caught by Inner.breakable, and the outer for loop continues. But if the second if condition is triggered, control of flow is sent to Outer.breakable, and both loops are exited. Running this object results in the following output:

i: 1, j: a
i: 1, j: b
i: 2, j: a

Use the same approach if you prefer labeled breaks. This example shows how you can use the same technique with just one break method call:

import scala.util.control._

val Exit = new Breaks
Exit.breakable {
    for (j <- 'a' to 'e') {
        if (j == 'c') Exit.break else println(s"j: $j")
    }
}

Discussion

If you don’t like using break and continue, there are several other ways to attack these problems.

For instance, if you want to add monkeys to a barrel, but only until the barrel is full, you can use a simple boolean test to break out of a for loop:

var barrelIsFull = false
for (monkey <- monkeyCollection if !barrelIsFull) {
    addMonkeyToBarrel(monkey)
    barrelIsFull = checkIfBarrelIsFull
}

Another approach is to place your algorithm inside a function, and then return from the function when the desired condition is reached. In the following example, the sumToMax function returns early if sum becomes greater than limit:

// calculate a sum of numbers, but limit it to a 'max' value
def sumToMax(arr: Array[Int], limit: Int): Int = {
    var sum = 0
    for (i <- arr) {
        sum += i
        if (sum > limit) return limit
    }
    sum
}
val a = Array.range(0,10)
println(sumToMax(a, 10))

A common approach in functional programming is to use recursive algorithms. This is demonstrated in a recursive approach to a factorial function, where the condition n == 1 results in a break from the recursion:

def factorial(n: Int): Int = {
    if (n == 1) 1
    else n * factorial(n - 1)
}

Note that this example does not use tail recursion and is therefore not an optimal approach, especially if the starting value n is very large. A more optimal solution takes advantage of tail recursion:

import scala.annotation.tailrec

def factorial(n: Int): Int = {
    @tailrec def factorialAcc(acc: Int, n: Int): Int = {
        if (n <= 1) acc
        else factorialAcc(n * acc, n - 1)
    }
    factorialAcc(1, n)
}

Note that you can use the @tailrec annotation in situations like this to confirm that your algorithm is tail recursive. If you use this annotation and your algorithm isn’t tail recursive, the compiler will complain. For instance, if you attempt to use this annotation on the first version of the factorial method, you’ll get the following compile-time error:

Could not optimize @tailrec annotated method factorial: it contains a recursive
call not in tail position