skip to content

How does in drive for-loop iteration over ranges, and how do you iterate descending or with a step?

level: seniorimportance: should knowfreq 45%

answer

  1. for-in uses iterator(), membership-in uses contains
  2. .. is ascending only; 5..1 is empty
  3. downTo to descend; step to skip
  4. downTo/step return IntProgression (step can be negative)
  5. Range for-loops compile to counter loops, no Iterator alloc

basics

~10 s

for (i in 1..5) loops 1 to 5 because the range can be iterated. To go down use downTo (5 downTo 1), and to skip values use step (1..10 step 2).

solid answer

~50 s

In a `for` header, `for (x in range)` uses the `iterator()` operator convention, not `contains`. `IntRange` implements `Iterable<Int>`, so it produces an iterator that walks each value ascending. A plain `..` range **cannot count down**: `for (i in 5..1)` is empty. To descend use the `downTo` infix function: `for (i in 5 downTo 1)`. To skip values use the `step` infix function: `for (i in 1..10 step 2)` yields 1,3,5,7,9. Both `downTo` and `step` return an **`IntProgression`** (a generalization of `IntRange` with an explicit step that can be negative). Progressions still support `in`, `first`, `last`, and `reversed()`. The compiler also performs a key optimization: iterating over a literal range or progression compiles to a **plain counter-based loop with no `Iterator` object allocated**, so `for (i in 0..<n)` is as cheap as a Java index loop.

code

kotlin · 6 lines
kotlin
for (i in 1..5) print(i)            // 12345
for (i in 5 downTo 1) print(i)       // 54321
for (i in 1..10 step 2) print("$i ") // 1 3 5 7 9
val p = 1..10 step 3
println(p.last)                      // 10 (clamped to grid)
println(9 in p)                      // false (not on step grid)

go deeper

for a junior

Knows for (i in 1..5) loops and downTo counts down.

for a middle

Uses step and downTo correctly and knows they yield progressions.

for a senior

Distinguishes iterator-in from contains-in, explains progression last clamping and step-grid membership.

for a principal

Reasons about the compiler's loop-lowering / zero-allocation guarantee and the Range-vs-Progression type design tradeoffs.

## Iteration uses iterator(), not contains When `in` appears in a `for` header, Kotlin requires the right operand to provide an `operator fun iterator()`. `IntRange` (via `Iterable<Int>`) does, so: ```kotlin for (i in 1..5) print(i) // 12345 ``` walks every value ascending. This is a **different convention** from membership: membership-`in` needs `contains`, iteration-`in` needs `iterator()`. ## Ascending only by default A `..` (or `..<`) range is always ascending. If the start exceeds the end it is **empty** — it does not count down: ```kotlin for (i in 5..1) print(i) // prints nothing (empty range) ``` ## downTo for descending Use the `downTo` infix function: ```kotlin for (i in 5 downTo 1) print(i) // 54321 ``` `5 downTo 1` returns an **`IntProgression`** with step -1. ## step to skip Use the `step` infix function (step must be a positive Int; direction is set by `..`/`downTo`): ```kotlin for (i in 1..10 step 2) print("$i ") // 1 3 5 7 9 for (i in 10 downTo 1 step 3) print("$i ") // 10 7 4 1 ``` Both produce an `IntProgression`. The `last` value is computed so the progression never overshoots the bound: `1..10 step 3` has `last == 10`, but `1..9 step 3` has `last == 7`. ## Range vs Progression - `IntRange` = a progression with step 1, also a `ClosedRange` (so it has a meaningful `contains`). - `IntProgression` = `first`, `last`, `step` (possibly negative). `downTo`/`step` yield this. It is `Iterable` and supports `in`, but its `last` is the last reachable element, not necessarily the nominal bound. ```kotlin val p = 1..10 step 3 println(p.last) // 10 println(10 in p) // true (10 is reachable) println(9 in p) // false (not on the step grid) ``` ## The allocation optimization The Kotlin compiler **special-cases** `for` loops over ranges and progressions: it lowers them to a counter-based loop using `first`, `last`, and `step` with **no `Iterator` instance allocated** on the heap. So `for (i in 0..<n)` has the same cost as a hand-written index loop — there is no per-iteration boxing or object churn for the standard primitive ranges. ## reversed() `(1..5).reversed()` returns an `IntProgression` 5..1 step -1, an alternative to `downTo`. ## Key APIs/keywords - `operator fun iterator()` (iteration convention) - `downTo`, `step`, `reversed()` (infix/functions) - `IntRange`, `IntProgression`, `.first`, `.last`, `.step` - compiler loop-lowering: no Iterator allocation for primitive ranges

  • Why does for (i in 5..1) print nothing?
    `..` always builds an ascending range; when start > end it is empty and iterates zero times. Use `5 downTo 1` to count down.
  • Does iterating a range allocate an Iterator?
    No. The compiler lowers for-loops over primitive ranges/progressions to a counter-based loop, so no Iterator object is allocated.
  • Is 9 in (1..10 step 3) true?
    No. The progression's reachable elements are 1,4,7,10; 9 is not on the step grid, so contains returns false even though 9 is between the bounds.

A range is a staircase you climb one step at a time; downTo turns you around, and step lets you take the stairs two at a time.

saying these in an interview costs you the question

  • Expecting 5..1 to count down
  • Thinking step can be negative (it must be positive; downTo sets direction)
  • Assuming range iteration allocates an Iterator per loop
  • Believing x in progression just checks the bounds (it checks the step grid too)
  • Confusing IntRange and IntProgression semantics for last

context