Start Learning
Javaneer
Back to stage
Stage 1·Syntax for Java Eyes

Control Flow & Ranges

for over ranges and collections, ranges as first-class values (1..10, until, downTo, step), and why Kotlin drops the C-style for loop for something more readable.

12 min readBeginner
On this page

Loops and iteration read more cleanly in Kotlin, largely because of one feature: ranges are first-class values. The C-style for (int i = 0; i < n; i++) loop - a fixture of Java since forever - is gone, replaced by iterating over a range or a collection directly. It's more readable, harder to get wrong (no off-by-one from a misplaced <=), and ranges turn out to be useful well beyond loops.

for iterates over things, not counters

Kotlin's for loop always iterates over something iterable - a range, a collection, an array. There's no three-part counter loop:

for (i in 1..5) println(i)              // 1 2 3 4 5 - iterate a range
for (name in names) println(name)        // iterate a collection directly
for (c in "hello") println(c)            // iterate a string's characters

To loop with an index over a collection, you use helpers rather than a manual counter:

for (i in names.indices) println(names[i])           // indices gives the valid index range
for ((i, name) in names.withIndex()) {               // both index and value, destructured
    println("$i: $name")
}

This eliminates the classic off-by-one bugs of for (int i = 0; i <= n; i++) (should it be < or <=?) - you name the range you mean, and the loop can't run past it.

Ranges are first-class

A range like 1..5 is a real object (IntRange), and Kotlin gives you a small vocabulary to build exactly the sequence you want:

1..5            // 1, 2, 3, 4, 5      (inclusive on both ends)
1..<5           // 1, 2, 3, 4         (exclusive end; older syntax: 1 until 5)
5 downTo 1      // 5, 4, 3, 2, 1      (descending)
1..10 step 2    // 1, 3, 5, 7, 9      (custom step)
'a'..'z'        // a range of characters

Because a range is a value, it's not just for loops - you use it in when (in 4..10, from the expressions lesson) and in membership checks:

if (age in 18..65) { ... }              // readable membership test
if (index !in list.indices) return      // "not in" - out of bounds check
val isDigit = c in '0'..'9'             // instead of c >= '0' && c <= '9'

The in operator (backed by ranges) turns clunky x >= lo && x <= hi comparisons into readable x in lo..hi - a small but constant improvement in intent-revealing code.

while and do-while (unchanged)

Not everything is different. while and do-while work exactly as in Java, for the cases where you genuinely need a condition-driven loop rather than iteration:

while (queue.isNotEmpty()) { process(queue.poll()) }

Kotlin keeps these as-is because they're already fine - the redesign is targeted at the error-prone C-style for, not iteration in general. (Kotlin also has break and continue, plus labels like outer@ for breaking out of nested loops, if you need them - rare, but available.)

Often you don't need an explicit loop at all

Ranges and for-loops are the imperative option, but idiomatic Kotlin frequently replaces loops with functional collection operations (map, filter, forEach, sum) - the subject of the collections module. (1..100).sum(), names.forEach { println(it) }, or list.filter { it > 0 }.map { it * 2 } often read better than an explicit loop. Reach for a range-based for when you genuinely need imperative iteration; otherwise the collection functions are usually cleaner. Both are available - pick the clearer one.

A guided tour vs. driving with an odometer

Java's C-style for loop is driving yourself while watching the odometer: 'start at mile 0, keep going while under mile 5, add one each time' - flexible, but you can misread the gauge and overshoot (the < vs <= off-by-one). Kotlin's for-in-range is a guided tour with a fixed itinerary: 'visit stops 1 through 5' - you name the destinations and simply can't drive past the last one. Ranges being first-class means that same itinerary is reusable off the road too: you can ask 'is this address on the tour route?' (age in 18..65) without driving it. And when you don't even need to visit each stop yourself, you hand the whole route to a service that processes it for you (map/filter) - you only take the wheel (an explicit loop) when the journey genuinely needs manual control.

Rewrite the loops

Convert to idiomatic Kotlin: (1) a Java for (int i = 0; i < items.size(); i++) that prints each item with its index; (2) a countdown for (int i = 10; i >= 1; i--); (3) a Java check if (score >= 0 && score <= 100). Use ranges and the appropriate helpers, and note one bug class this style prevents.

Why does Kotlin replace Java's C-style for loop with for-in over ranges and collections?

Key takeaways

  • Kotlin's for loop iterates over something (a range, collection, or string) - there's no C-style counter loop, which eliminates off-by-one boundary bugs.
  • For an index, use collection.indices or withIndex() (destructured to (index, value)) rather than a manual counter.
  • Ranges are first-class values: 1..5 (inclusive), 1..<5 / until (exclusive end), downTo (descending), step (custom stride), and character ranges.
  • Because ranges are values, the 'in' operator gives readable membership tests (age in 18..65, c in '0'..'9') replacing clunky >= && <= comparisons.
  • while/do-while are unchanged; and idiomatic Kotlin often replaces explicit loops entirely with functional collection operations (map/filter/forEach).
Was this lesson helpful?
Edit this page on GitHub