Can a Kotlin for loop run forever?
In some languages a for loop has an update statement that the programmer writes, and a mistake in that statement can make the loop run forever. A Kotlin for (x in range) loop is different. It walks through a fixed sequence of values, and when the sequence is used up, the loop is over. The loop variable cannot be changed inside the body: x-- fails to compile with 'val' cannot be reassigned.
This is a safety feature. If you rewrite a runaway while loop as a for loop over a range, the compiler and the range make sure it ends. The lesson's exercise does exactly that.
The broken program
This program should print the odd numbers from 15 down to 1 and then stop. It never stops:
var number = 15
while (number != 0) {
println(number)
number++
}
When you run it, you see 15, 16, 17, and so on, with bigger and bigger numbers. Two separate mistakes are involved.
Mistake 1: the counter moves the wrong way
number++ adds 1 on every pass. The loop waits for number to reach 0, and the value starts at 15 and moves away from 0. Adding 1 only gets back to 0 after the 32-bit Int passes 2147483647 and wraps around to a negative number, billions of passes later, so for practical purposes the program never stops.
Mistake 2: the condition waits for an exact value
Suppose the update were number -= 2. The values would be 15, 13, ..., 3, 1, -1, and so on. They are all odd, so none of them is 0, and the condition number != 0 would still be true forever. Even a correct direction fails here, because the step skips over the exact value being tested.
The program also has a third problem that is not about stopping: with a step of 1, even a working loop would print every number, not only the odd ones.
Fixing it step by step
- Replace the manual counter with a range. The loop should cover the values 15, 13, ..., 1. A progression describes that directly, so the
var numberdeclaration and thenumber++line both go away. - Count down. The sequence goes from 15 to 1, so use
15 downTo 1. The right end is 1 and is included. - Take steps of 2. Add
step 2. The step is positive, as Kotlin requires. The direction comes fromdownTo. - Write the loop with its own variable. The variable
numberis now declared in the loop header, so the name from the original program stays the same.
The solution in Kotlin
for (number in 15 downTo 1 step 2) {
println(number)
}
It prints 15, 13, 11, 9, 7, 5, 3 and 1 on separate lines, and then the loop ends by itself. The next value would be -1, which is below the end of the range.
Why this loop cannot hang
- The sequence is finite.
15 downTo 1 step 2has exactly eight values, and the loop takes them one by one. - The step is checked. A step of 0 or a negative step throws an
IllegalArgumentExceptionwith the messageStep must be positive, was: 0.That is an error you see at once, rather than a program that runs forever. - The comparison is a range, not an exact match. The loop does not test
number != 0. It asks whether there are values left, so skipping over any particular number does not matter.
When you do need an infinite loop
Sometimes a program should run until something happens, such as a user typing "quit". Kotlin's way to write this is while (true) with a break inside:
var attempts = 0
while (true) {
attempts++
if (attempts == 3) break
}
println(attempts)
This prints 3. The loop is infinite in form but has a clear exit. If you cannot point to the exit, the loop is a bug.
Common mistakes
- Keeping the manual counter next to a range loop. A loop over a range does not need a
varoutside it. - Using a negative step.
15 downTo 1 step -2throws an exception. UsedownTofor the direction and a positive step. - Ending at 0 instead of 1.
15 downTo 0 step 2still prints the same eight numbers, because the steps skip over 0. Ending at 1 says what you mean. - Starting at the wrong parity. Starting at 16 with a step of 2 would print even numbers.
Key takeaways
- A
forloop over a range or progression ends when its values run out, and its loop variable cannot be modified in the body. - Waiting for an exact value with
!=hangs the loop whenever the counter steps over it, and counting in the wrong direction never reaches it. - The fix here is
for (number in 15 downTo 1 step 2). - For deliberate endless loops, use
while (true)with abreak.