skip to content

How does Kotlin's for loop decide that a type is iterable, and how would you make a custom type (or even a range of a custom type) usable in a for loop? What desugaring happens under the hood?

level: seniorimportance: should knowfreq 35%

answer

  1. for desugars to iterator()/hasNext()/next()
  2. operator convention, not Iterable interface
  3. member OR extension functions work
  4. iterator { yield(...) } builds lazily
  5. extend ClosedRange<T>.iterator() for custom ranges

basics

~20 s

A for loop works on anything that provides an iterator. You give your type an iterator() function (or add one as an extension), returning an object with hasNext() and next(). Then for just works on it.

solid answer

~40 s

Kotlin's `for (x in c)` is purely convention-based, resolved at compile time. The compiler desugars it to: obtain `c.iterator()`, then `while (it.hasNext()) { val x = it.next(); ... }`. So `c` must have an `operator fun iterator()` returning a type with `operator fun hasNext(): Boolean` and `operator fun next(): T`. These can be **members or extension functions**, and `iterator()` doesn't have to come from the `Iterable` interface — duck typing on the operator name is enough. To make a custom type iterable, declare `operator fun MyType.iterator(): Iterator<E>` (returning a standard `Iterator` is easiest). You can even make a `ClosedRange<MyType>` iterable by giving it an `operator fun iterator()`, which is how `1..10`-style ranges of custom comparable types work. The whole mechanism is zero-interface-coupling: it relies on operator-function resolution, not subtyping.

code

kotlin · 11 lines
kotlin
operator fun IntRange.iterator2(): Iterator<Int> = object : Iterator<Int> {
    var cur = first
    override fun hasNext() = cur <= last
    override fun next() = cur++
}

// Lazy builder form
operator fun MyType.iterator(): Iterator<Int> = iterator {
    var x = 0
    while (x < 3) yield(x++)
}

go deeper

for a junior

Can use a for loop over built-in collections but may not know the underlying contract.

for a middle

Knows for needs an iterator() and can implement a basic Iterator with hasNext()/next().

for a senior

Explains the operator-convention desugaring, member-or-extension resolution, and adds iteration via extensions including the iterator { yield } builder.

for a principal

Discusses the zero-coupling design, ClosedRange<T> extension iterators, and primitive-specialized iterators avoiding boxing.

## Convention over interface Kotlin's `for` does **not** require implementing `Iterable`. It uses **operator conventions** resolved by name and signature at compile time (a form of structural/duck typing for operators). ## The desugaring ```kotlin for (x in c) { body } ``` compiles to roughly: ```kotlin val it = c.iterator() while (it.hasNext()) { val x = it.next() body } ``` So three operator functions must resolve: - `c.iterator()` — `operator fun iterator(): I` - `it.hasNext()` — `operator fun hasNext(): Boolean` - `it.next()` — `operator fun next(): T` All three may be **members or extensions**. Returning a standard `kotlin.collections.Iterator<T>` gives you `hasNext()`/`next()` for free. ## Making a custom type iterable ```kotlin class Grid(val rows: Int, val cols: Int) operator fun Grid.iterator(): Iterator<Pair<Int, Int>> = iterator { for (r in 0 until rows) for (c in 0 until cols) yield(r to c) } for ((r, c) in Grid(2, 2)) println("$r,$c") // 0,0 0,1 1,0 1,1 ``` Here `iterator { ... }` is the standard library builder using `yield`, which constructs an `Iterator` lazily. ## Making a range of a custom type iterable For a comparable type, `a..b` produces a `ClosedRange<T>` (via `Comparable`), but that range isn't iterable by default. Add an extension iterator: ```kotlin class MyDate(val epochDay: Long) : Comparable<MyDate> { override fun compareTo(other: MyDate) = epochDay.compareTo(other.epochDay) } operator fun ClosedRange<MyDate>.iterator(): Iterator<MyDate> = object : Iterator<MyDate> { var current = start.epochDay override fun hasNext() = current <= endInclusive.epochDay override fun next() = MyDate(current++) } for (d in MyDate(0)..MyDate(3)) { /* iterates days */ } ``` Note `start`/`endInclusive` come from `ClosedRange`, and `..` comes from the `rangeTo` provided for `Comparable`. ## Why this design - **Zero coupling:** no need to implement an interface you don't own; add iteration via extensions. - **Performance:** primitive ranges (`IntRange`) iterate without boxing because their iterators expose `nextInt()`-style specializations the compiler uses. ## Summary `for` = `iterator()` + `hasNext()` + `next()` as operator functions (member or extension). Implement them (often via the `iterator { yield(...) }` builder or a standard `Iterator`) to make any type — or a `ClosedRange` of it — loopable.

  • Does a type need to implement Iterable to be used in a for loop?
    No. It just needs an operator iterator() (member or extension) returning something with hasNext()/next(); it's convention-based, not interface-based.
  • How can you add iteration to a type you don't control?
    Declare an extension: operator fun ThirdPartyType.iterator(): Iterator<E>. The for loop will resolve it at compile time.

saying these in an interview costs you the question

  • Insisting the type must implement Iterable/Iterator
  • Forgetting the operator keyword on iterator/hasNext/next
  • Not knowing extensions can supply iterator()
  • Confusing the iterator() builder (yield) with Java's Iterable contract

context