Start learning free

LESSON 113 OF 338 · FOR LOOPS - LOOPING OVER ARRAYS

Infinite For Loops in Go: Growing a Slice While You Loop

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:

fruits := []string{"banana", "orange"}

for i := 0; i < len(fruits); i++ {
    fruits = append(fruits, "kivi")
}

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

As a result, we never make it to the end of the array. It keeps growing forever!

This is an infinite for 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

    GO · FOR LOOPS - LOOPING OVER ARRAYS

    Infinite For Loops 2 in Go, explained

    Looping over a slice that changes

    A for loop is often used to walk through a sequence such as a slice. Trouble starts when the loop changes the sequence it is walking through. Whether the loop ends then depends on how you wrote it.

    The classic infinite loop

    Here the loop condition asks how long the slice is, and the body makes it longer:

    fruits := []string{"banana", "orange"}
    
    for i := 0; i < len(fruits); i++ {
        fruits = append(fruits, "kivi")
    }
    

    Each pass adds one element and increases i by one. The slice starts with two elements, so len(fruits) is 2 at the first check. After the first pass it is 3 while i is 1. After the second pass it is 4 while i is 2. The length is always one step ahead of i, so the condition i < len(fruits) never becomes false. The loop never reaches the end of the slice because the end keeps moving away. The program never stops, and in the end it would run out of memory.

    If you want to see the pattern without hanging your program, add a guard that breaks out of the loop, and print the two values:

    for i := 0; i < len(fruits); i++ {
        fruits = append(fruits, "kivi")
        fmt.Println(i, len(fruits))
        if i == 3 {
            break
        }
    }
    

    This prints:

    0 3
    1 4
    2 5
    3 6
    

    The length is always ahead of the index. The break is only there for the experiment. A real loop should end because its condition becomes false.

    Range behaves differently

    A for ... range loop does not recheck the length on every pass. Go looks at the slice once, when the loop starts, and walks over the elements that were there at that moment. If the body appends to the same slice, the loop does not see the new elements:

    nums := []int{1, 2, 3}
    for _, n := range nums {
        nums = append(nums, n*10)
    }
    fmt.Println(nums)
    

    This ends after three passes and prints:

    [1 2 3 10 20 30]
    

    So a range loop that appends to its own slice finishes. It is the form with i < len(...) that runs forever. Still, changing a slice while you walk through it is confusing to read, and it is usually better to collect new elements in a separate slice.

    The exercise

    The code should copy every name from newStudents to the end of students. The result must be Andy, Mary, Alex, Ana, Alexa and Mike. The code we start from is:

    students := []string{"Andy", "Mary", "Alex"}
    newStudents := []string{"Ana", "Alexa", "Mike"}
    
    for _, student := range newStudents {
        newStudents = append(newStudents, student)
    }
    

    The loop is the right shape: it visits each new student. The mistake is in the body. It appends to newStudents, the slice being walked through, so students never changes. The loop still ends after three passes, because of the way range works, but newStudents has been doubled to six names and students still holds only the original three.

    The fix is to append to students and assign the result back to students:

    students := []string{"Andy", "Mary", "Alex"}
    newStudents := []string{"Ana", "Alexa", "Mike"}
    
    for _, student := range newStudents {
        students = append(students, student)
    }
    

    Now newStudents is only read, and students grows from three names to six: [Andy Mary Alex Ana Alexa Mike].

    Why append is assigned back

    append does not change a slice in place. It returns a slice, because the old backing array may be too small and a new, larger one may have to be allocated. That is why the line reads students = append(students, student). Calling append(students, student) and throwing the result away would leave students unchanged, and Go would reject it with a compile error since the result is unused.

    Key takeaways

    • A loop whose condition depends on the length of a slice must not make that slice grow on every pass, or it never ends.
    • A range loop walks over the elements that existed when it started.
    • Append to the slice you want to grow, and assign the result of append back to it.
    • Prefer building a second slice over modifying the one you are iterating over.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free