Ranges and loops
Counting with ranges, the until, step and downTo that adjust them, looping over a list of values, and the while loop for when the count is unknown.
A range is a stretch of numbers
1..5 is a range: the numbers from one to five, both ends included. for (i in 1..5) runs its body once for each of them, with i holding the current number. Ranges are values like any other, so nothing special happens at the loop — the loop just walks along the range.
Three words reshape a range. until leaves the last number out, so 1 until 5 is one to four and is what you want when the count starts at zero. step says how far to jump, and downTo counts backwards. They combine in the order you would say them: 10 downTo 0 step 5. The end is still included in a downTo, and step must be positive even when the direction is backwards.
Looping over values, not just numbers
The same for walks any list of values: for (name in names) gives you each name in turn, with no index to manage and no chance of being off by one. That is the loop you will write most often, and the next chapters lean on it. A list of numbers and a range behave the same way here, because both can hand out one item at a time.
Where a range answers "how many times", a list answers "for each of these". If you need the position as well, keep a counter of your own — or reach for the list tools in chapter eight, which do that work for you. Keeping the two questions apart makes the loop easier to read: the list supplies the values, and the body decides what to do with one.
While, and the accumulator habit
while repeats as long as a condition stays true, which is the right tool when you do not know the count in advance. It is easier to get wrong than a range, because nothing stops you from writing a condition that never becomes false — the program then runs until the runtime gives up. The condition is checked before the first pass, so a false start means zero passes rather than one.
Inside a loop you often build up an answer: start a var before the loop, add to it in each pass, print it after. That pattern — accumulator in, loop, answer out — is worth recognising, because most loops are some version of it, and the list tools in chapter eight are the same pattern with the bookkeeping removed.
Off by one, and how to see it
The two off-by-one mistakes are easy to name and easy to make. A loop that should run five times but uses 1 until 5 runs four; a loop that should stop before a list’s last item but uses 1..5 runs five. The count is decided by the range, not by the body, so the fix belongs on the for line.
When the output looks short or long by exactly one line, count the range before changing anything inside the loop. Print the loop variable on its own for a small case, or use withIndex() when the position matters. A boundary that is tested once with a tiny range is a boundary you will not have to debug in a longer one.
The four shapes of for, and one while
fun main() {
for (i in 1..5) print("$i ") // 1 2 3 4 5
for (i in 1 until 5) print("$i ") // 1 2 3 4
for (i in 0..10 step 5) print("$i ") // 0 5 10
for (i in 3 downTo 1) print("$i ") // 3 2 1
println()
val names = listOf("Ada", "Grace", "Alan")
for (name in names) println("Hello, $name")
var total = 0
for (i in 1..4) total += i
println("Sum 1 to 4: $total") // 10
var count = 3
while (count > 0) {
println(count)
count -= 1
}
}Counting several ways
Run it and watch how each range prints. Then change the range in the first loop to 1..3, add a loop that counts down from 5 to 1, and make the sum loop add the numbers 1 to 10 instead.
A loop is a walk over a sequence
A range is not special syntax; it is a value that can be stored and passed around. for asks that value for its items one at a time, using an iterator, and runs the body once per item. The loop variable is a fresh read-only name on each pass, which is why assigning to it inside the body is refused: the next value comes from the sequence, not from your arithmetic.
while has no sequence behind it. It checks its condition before every pass, including the first, so a condition that starts false runs the body zero times. That difference is the rule for choosing: use for when you are walking something you already have, and while when the next pass depends on work the previous one did.
Three loop mistakes cost the most time. 1..5 includes 5, while 1 until 5 stops at 4; pick the one that matches the count you mean. 5..1 is empty rather than descending, so a countdown needs downTo. And a while whose condition never changes runs until the runner kills it — make sure something inside the body moves the condition toward false.
Try these next
- Print the even numbers from 2 to 10 with
step 2, then with anifinside a plain range. - Sum the numbers from 1 to 100 with a
whileloop, then check the answer with a range. - Walk a list with
withIndex()and print a numbered line for each item.
A countdown, a total, and numbered names
Run it and check the sequence each loop produces. Then change the countdown step to 3, make the while loop reach zero in one pass, and add a numbered line for a fourth name.
The end of a range is included, and the end of an until is not. Writing one where you meant the other is the most common loop mistake in any language: the loop runs once too many, or once too few, and nothing complains.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
How many numbers does for (i in 1 until 5) produce?
