RizTech Academy logo
RizTech Academy
Language BasicsLesson 5 of 520 min

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

  1. Print 1..5, 1..<5, 5 downTo 1, and 1..10 step 2, and confirm each matches the lesson.
  2. Check 25 in 18..60 and 70 in 18..60. Rewrite the first as a && comparison and decide which reads better.
  3. Loop over a list of cities with withIndex and print "index: city" for each.
  4. Write a while loop that counts down from 5 to 1, then a do-while that loops until the user types quit.
  5. In a for over 1..10, use continue to skip multiples of 3 and break when you reach 8.
  6. 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.
  7. Find a for loop in code you have written that only builds a list, and rewrite it as a collection operation.

Official documentation

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