Start learning free

LESSON 113 OF 348 · FOR LOOPS - LOOPING OVER ARRAYS

Infinite For Loops in Kotlin: Changing a List 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 = mutableListOf("banana", "orange")

for (fruit in fruits) {
    fruits.add("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.

Kotlin stops this for us: after the first item is added, the loop's next step throws a ConcurrentModificationException (the list was changed while the loop was going through it).

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

    KOTLIN · FOR LOOPS - LOOPING OVER ARRAYS

    Infinite For Loops 2 in Kotlin, explained

    The idea

    A for loop over a collection visits its elements one by one, and it stops when it has visited the last one. That only works if the collection has a fixed end. If the loop body keeps adding elements to the collection it is walking through, the end keeps moving away. A loop that never reaches its end is an infinite loop: the program runs forever and is unusable.

    Here is a loop that tries to do exactly that:

    val fruits = mutableListOf("banana", "orange")
    
    for (fruit in fruits) {
        fruits.add("kivi")
    }
    

    Every pass adds a "kivi" to the end of fruits, so there is always one more element to visit.

    What Kotlin does about it

    On the JVM, the list's iterator remembers how many changes the list had when the loop started. After the first add, the next step of the loop sees that the list was modified behind its back and throws a ConcurrentModificationException. The program stops with an error instead of growing the list forever.

    That is helpful, but you shouldn't rely on it. The exception is a safety net, not a rule that lets you modify a list inside its own loop. It isn't thrown every time. If a loop removes elements, the iterator can decide that it has reached the end and stop quietly. After for (x in r) { r.remove(x) } on mutableListOf("a", "b"), the list still holds [b], and nothing was reported. A loop that silently skips elements is a bug that is harder to spot than a crash.

    The rule is simple: don't add elements to, or remove them from, the collection a for loop is iterating over.

    The exercise

    The goal is to add every name from newStudents to the end of students, so that students holds Andy, Mary, Alex, Ana, Alexa and Mike. This is the code you start with:

    val students = mutableListOf("Andy", "Mary", "Alex")
    val newStudents = mutableListOf("Ana", "Alexa", "Mike")
    
    for (student in newStudents) {
        newStudents.add(student)
    }
    

    It stops with a ConcurrentModificationException. The mistake is in which list receives the new element:

    1. Find the list the loop walks over. The header says for (student in newStudents), so newStudents is the list being iterated.
    2. Find the list the body changes. The body calls newStudents.add(student). It is the same list, so the loop modifies what it is reading. After the first pass newStudents has four elements, [Ana, Alexa, Mike, Ana], and the iterator throws.
    3. Change the target. The names should end up in students, so the body must call students.add(student). Now the loop reads one list and writes to another, and the list being read never changes.
    val students = mutableListOf("Andy", "Mary", "Alex")
    val newStudents = mutableListOf("Ana", "Alexa", "Mike")
    
    for (student in newStudents) {
        students.add(student)
    }
    

    The loop makes three passes, one per name in newStudents, and adds each to the end of students, which then has six elements in the required order.

    Kotlin details

    • mutableListOf is what allows add. A list made with listOf is read-only and has no add method. Both lists here are mutable, so the first version compiles and fails only when it runs.
    • val does not make the list constant. It fixes which list the name refers to. You can still add elements to a list stored in a val.
    • Looping and adding is one way to merge. The call students.addAll(newStudents) does the same job with no loop at all.
    • Looping over a copy. If you really must change a list while visiting each element, iterate over a snapshot: for (x in list.toList()). The copy doesn't change, so the loop ends normally, while changes go to the original.

    Common mistakes

    • Adding to the list you are looping over. This is the bug in the starting code. Check the name before the dot in .add(...) against the name in the loop header.
    • Removing elements inside the loop. It can throw, or it can silently skip elements, depending on the list's size and position.
    • Thinking the exception means Kotlin is protecting you from every infinite loop. A while loop whose condition never becomes false has no such safety net.

    Key takeaways

    • Don't change a collection while a for loop is going through it.
    • Kotlin on the JVM often reports this with a ConcurrentModificationException, but not always.
    • Read from one collection and write to another, or loop over a copy.
    • Check which list each line of the body touches.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free