Start learning free

LESSON 118 OF 414 · FOR LOOPS - LOOPING OVER ARRAYS

Infinite For Loops in Scala: Changing a Buffer While Looping

Lesson Read the full explanation

We also use for loops to iterate over sequences like strings and arrays. We can run into problems when we manipulate a sequence while iterating on it.

For example, if we append elements to an array while iterating on it:

val fruits = scala.collection.mutable.ArrayBuffer("banana", "orange")

for (fruit <- fruits) {
  fruits += "kivi"
}

Every time we enter this loop, we add a kivi item to the end of the list that we are iterating through, so we would never make it to the end of the list: it would keep growing forever.

Scala stops this for us: after the first item is added, the loop's next step throws a ConcurrentModificationException ("mutation occurred during iteration").

Changing a collection while looping over it is always a bug. And when nothing stops the loop, the collection keeps growing forever: an infinite loop. As programmers, we want to make sure we never write infinite loops, as they make our program run forever and completely unusable.


Assignment
Follow the Coding Tutorial and let's practice with infinite for loops!


Hint
Look at the examples above if you get stuck.


STEP 1 OF 1
    Ctrl + Enter to check

    SCALA · FOR LOOPS - LOOPING OVER ARRAYS

    Infinite For Loops 2 in Scala, explained

    Looping over a collection that changes

    A for loop walks through a collection one element at a time. That is safe as long as the collection stays the same during the walk. Trouble starts when the loop body changes the very collection being walked, for example by adding elements to it.

    In Scala, only a mutable collection can grow, such as scala.collection.mutable.ArrayBuffer. A plain Array has a fixed size and has no += method, and the default List is immutable. Here is a loop that adds to the buffer it is reading:

    val fruits = scala.collection.mutable.ArrayBuffer("banana", "orange")
    
    for (fruit <- fruits) {
      fruits += "kivi"
    }
    

    Each pass adds a new element to the end. If the loop kept going until it reached the end, it would never get there: every pass moves the end one step further away. That is an infinite loop, a program that never finishes.

    What Scala does about it

    An ArrayBuffer keeps track of how many times it has been modified. Its iterator checks that count on each step, and when the buffer has changed since the loop began, it throws an exception instead of continuing. The loop above stops after the first pass with:

    java.util.ConcurrentModificationException: mutation occurred during iteration
    

    This check is a safety net, not a feature to rely on. Other collections may react differently, and the lesson is that changing a collection while looping over it is a bug whichever way it fails.

    The exercise

    The editor holds two buffers: students with "Andy", "Mary" and "Alex", and newStudents with "Ana", "Alexa" and "Mike". The goal is to add every name from newStudents to the end of students. The starter loop reads newStudents and, in its body, appends to newStudents too:

    val students = scala.collection.mutable.ArrayBuffer("Andy", "Mary", "Alex")
    val newStudents = scala.collection.mutable.ArrayBuffer("Ana", "Alexa", "Mike")
    
    for (student <- newStudents) {
        newStudents += student
    }
    

    It reads from a buffer and writes to the same one, so it throws after the first pass. On that pass newStudents has become ArrayBuffer(Ana, Alexa, Mike, Ana), and students still has only its three original names.

    1. Decide which buffer is read and which is written. The loop should read newStudents and write to students.
    2. Keep the header. for (student <- newStudents) is already right.
    3. Change the target of += from newStudents to students. Now the buffer being walked is never touched.
    val students = scala.collection.mutable.ArrayBuffer("Andy", "Mary", "Alex")
    val newStudents = scala.collection.mutable.ArrayBuffer("Ana", "Alexa", "Mike")
    
    for (student <- newStudents) {
        students += student
    }
    

    After the loop, students holds Andy, Mary, Alex, Ana, Alexa, Mike, in that order. The solution only builds the buffer. To see the six names, loop over students afterwards and print each one.

    Scala details

    • += on a buffer appends. On an ArrayBuffer, buffer += x adds x at the end and changes the buffer in place. Even though students is a val, that is allowed: val stops you from pointing students at a different object, not from changing the object's contents.
    • An Array can't grow. arr += 3 on an Array[Int] doesn't compile: value += is not a member of Array[Int].
    • Looping over a copy is safe. If you really want to add to a buffer for every element it already has, loop over a snapshot such as buffer.toList. Starting from ArrayBuffer(1, 2, 3), for (x <- buffer.toList) buffer += x ends with ArrayBuffer(1, 2, 3, 1, 2, 3), because the loop walks the copy.

    Key takeaways

    • Don't add to or remove from a collection while a for loop is walking it.
    • An ArrayBuffer detects this and throws ConcurrentModificationException.
    • Read from one collection and write to another, or loop over a copy.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free