Loops and ranges
Kotlin has the loops you expect — for and while — but with a twist worth internalising early:
in idiomatic Kotlin you write far fewer explicit loops than in most languages, because
collection operations (the collections module) usually say what you mean more clearly. This lesson
covers the loops and ranges you do need, and points at what replaces the rest.
Ranges
A range is a sequence of values from one bound to another, written with ..:
val oneToFive = 1..5 // 1, 2, 3, 4, 5 (both ends included)
Kotlin's ranges are inclusive of both ends by default, which reads naturally. The variants:
1..5 // 1 2 3 4 5 — inclusive of both
1..<5 // 1 2 3 4 — excludes the upper bound (1 until 5)
5 downTo 1 // 5 4 3 2 1 — counting down
1..10 step 2 // 1 3 5 7 9 — every second value
'a'..'e' // a b c d e — ranges work on Chars too
1..<5 (or the older 1 until 5) is the one to reach for when you want "up to but not including" —
common when indexing, since a list of size n has indices 0..<n. downTo counts backwards, and
step changes the interval. You can also test membership with in, which you met in when:
val age = 25
println(age in 18..60) // true
println(age !in 18..60) // false
in 18..60 is clearer than age >= 18 && age <= 60, and it is the idiomatic way to write a range
check.
The for loop
Kotlin's for iterates over anything that can be iterated — a range, a list, a string, a map:
for (i in 1..5) print("$i ") // 1 2 3 4 5
for (city in listOf("Pune", "Mumbai")) println(city)
for (c in "Pune") print("$c ") // P u n e
for (i in 5 downTo 1) print("$i ") // 5 4 3 2 1
for (i in 0..<10 step 2) print("$i ") // 0 2 4 6 8
There is no C-style for (int i = 0; i < n; i++) in Kotlin — the range form replaces it and is
harder to get wrong (no off-by-one from a mistyped condition). When you need the index as well as
the value, use withIndex rather than manual counting:
val cities = listOf("Pune", "Mumbai", "Nagpur")
for ((index, city) in cities.withIndex()) {
println("$index: $city") // 0: Pune, 1: Mumbai, 2: Nagpur
}
That (index, city) is a destructuring declaration — unpacking a pair into two names — which
you will see again with maps and data classes.
while and do-while
For "loop until a condition changes", where you do not know the count in advance:
var countdown = 3
while (countdown > 0) {
println(countdown)
countdown-- // -- decrements; ++ increments
}
println("Go!")
do-while runs the body at least once, checking the condition after:
var input: String?
do {
print("Type 'quit': ")
input = readLine()
} while (input != "quit")
while is right for reading input until a sentinel, polling, or anything genuinely open-ended.
Note while needs a var (something changes each pass) — one of the legitimate reasons to use
var rather than val.
break and continue
Inside any loop:
for (i in 1..10) {
if (i == 5) break // stop the loop entirely
if (i % 2 == 0) continue // skip to the next iteration
print("$i ") // 1 3
}
break exits the loop; continue skips the rest of this pass. Use them sparingly — a loop that
needs several breaks and continues is often clearer rewritten as a collection operation with a
condition.
The bigger point: prefer collection operations to loops
Here is the habit that separates idiomatic Kotlin from Kotlin-written-like-Java. A great many loops exist only to transform or filter a collection, and Kotlin has clearer tools for that. Compare:
// the loop you might reach for out of habit
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evensDoubled = mutableListOf<Int>()
for (n in numbers) {
if (n % 2 == 0) {
evensDoubled.add(n * 2)
}
}
println(evensDoubled) // [4, 8, 12]
// the idiomatic Kotlin
val idiomatic = numbers.filter { it % 2 == 0 }.map { it * 2 }
println(idiomatic) // [4, 8, 12]
The second version says what you want — "the even ones, doubled" — rather than how to loop and
accumulate. It is shorter, has no mutable list to get wrong, and reads top to bottom like the
sentence it is. This is a whole module (collections) later; for now, the lesson is: when you find
yourself writing a for loop that builds a list, ask whether filter, map or one of their
friends would say it better. Usually it would.
Explicit loops still earn their place — side effects (printing, writing to a file), genuine
open-ended while loops, and performance-critical inner loops. But reaching for a loop by
default is a habit worth unlearning.
Check your work
What .. produces, and whether it includes both ends. A range, inclusive of both bounds.
How to exclude the upper bound. 1..<5 (or 1 until 5) — useful for indices 0..<n.
How to count down, and to step. 5 downTo 1, and 1..10 step 2.
The idiomatic range check. x in 18..60, clearer than two comparisons with &&.
Why there is no C-style for. The range form replaces it and avoids off-by-one condition
errors.
How to get the index and value together. for ((index, value) in list.withIndex()) — a
destructuring declaration.
When to use while, and do-while. Open-ended loops; do-while runs the body at least once.
Why while typically needs a var. Something must change each pass to end the loop.
What break and continue do. Exit the loop; skip to the next iteration — use sparingly.
The habit that marks idiomatic Kotlin. Prefer collection operations (filter, map) over loops
that build a list.
Practice
- Print
1..5,1..<5,5 downTo 1, and1..10 step 2, and confirm each matches the lesson. - Check
25 in 18..60and70 in 18..60. Rewrite the first as a&&comparison and decide which reads better. - Loop over a list of cities with
withIndexand print"index: city"for each. - Write a
whileloop that counts down from 5 to 1, then ado-whilethat loops until the user typesquit. - In a
forover1..10, usecontinueto skip multiples of 3 andbreakwhen you reach 8. - Write the "even numbers doubled" loop with a mutable list, then rewrite it as
.filter { }.map { }. Confirm both give[4, 8, 12]and decide which you prefer. - Find a
forloop in code you have written that only builds a list, and rewrite it as a collection operation.
Official documentation
- Kotlin — Conditions and loops —
for,while,break,continue. - Kotlin — Ranges and progressions —
..,..<,downTo,step, andin. - Kotlin — Collections overview — Where the operations that replace most loops live.
Next module — Null Safety: Kotlin's single best feature, and the reason it exists.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship