Start learning free

LESSON 118 OF 415 · FOR LOOPS - LOOPING OVER ARRAYS

Infinite Loops and Changing an Array in Swift

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:

var fruits = ["banana", "orange"]

for _ in fruits {
    fruits.append("kivi")
}

Every time we enter this loop, we add a kivi item to the end of the array that we are iterating through.

Swift arrays are values, so the for loop goes through a copy of fruits made when the loop starts. This loop runs only twice and fruits ends up with 4 items. It is not infinite, but it hides a trap: a loop that checks the array's current size on every pass never ends:

var i = 0
while i < fruits.count {
    fruits.append("kivi")
    i += 1
}

This loop never makes it to the end of the array. It keeps growing forever!

This is an infinite loop. You can imagine that 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

    SWIFT · FOR LOOPS - LOOPING OVER ARRAYS

    Infinite For Loops 2 in Swift, explained

    The problem: changing a sequence while looping over it

    Loops walk over sequences such as arrays and strings. Trouble starts when the loop body changes the very sequence it is walking over. In some languages that makes a loop run forever. Swift behaves differently for for-in, but the same idea can still produce an infinite loop elsewhere.

    A for-in loop works on a copy

    Consider this code:

    var fruits = ["banana", "orange"]
    
    for _ in fruits {
        fruits.append("kivi")
    }
    

    Each pass appends an item to the array being iterated. Swift arrays are values, so the loop walks through a copy of fruits made when the loop starts. That copy holds two items, so the body runs twice, and afterwards fruits has four items. The loop is not infinite. The same holds for for _ in 0..<fruits.count: the range is computed once, before the first pass, and also gives two passes.

    A while loop re-reads the size

    The trap appears when the condition is checked on every pass against the array's current size:

    var i = 0
    while i < fruits.count {
        fruits.append("kivi")
        i += 1
    }
    

    Every pass adds one item and increases i by one, so i never catches up with fruits.count: after a pass, count is always two larger than i. The condition stays true and the loop never ends. This is an infinite loop. It keeps the program running and growing memory until the program is stopped or crashes, so the code is unusable.

    A bounded version shows the pattern. Stopping after five passes, starting from the two-item array, leaves i at 5 and the array at 7 items, and the condition is still true.

    The exercise

    The starting code should add every name from newStudents to the end of students:

    var students = ["Andy", "Mary", "Alex"]
    let newStudents = ["Ana", "Alexa", "Mike"]
    
    for student in newStudents {
        newStudents.append(student)
    }
    

    It does not compile. The compiler says cannot use mutating member on immutable value: 'newStudents' is a 'let' constant. Two things are wrong at once: the loop walks over newStudents, which is right, but it appends to newStudents as well. That is the pattern this lesson warns about, and because newStudents is a let, Swift refuses it.

    1. Read the loop header. It walks over the source array, newStudents. Keep it.
    2. Find the array that must grow. The target is students, declared with var, so it can change.
    3. Append to the target. Change the body to students.append(student).
    var students = ["Andy", "Mary", "Alex"]
    let newStudents = ["Ana", "Alexa", "Mike"]
    
    for student in newStudents {
        students.append(student)
    }
    

    Now the loop reads from one array and writes to another. It runs three times and students ends as ["Andy", "Mary", "Alex", "Ana", "Alexa", "Mike"].

    Common mistakes

    • Changing let to var on newStudents. The code would compile, but it would append to the array it is iterating and never fill students.
    • Appending to the array you loop over. With for-in it finishes, but the result is rarely what you meant. Loop over one array and build another.
    • Forgetting to advance a while loop. Without i += 1 the condition never changes. Swift has no ++, so write += 1.

    Key takeaways

    • A for-in loop over an array uses a copy made when the loop starts, so appending inside it does not make it endless.
    • A while condition that reads the current size can chase a growing array forever.
    • A let array cannot be changed. Read from one array and append to a different var array.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free