Polyglot
Languages Kotlin loops
Kotlin § loops

Loops

Kotlin admits three principal iteration forms: for-in (the conventional iterator-based form — admits any Iterable<T> or built-in range), while (loop while condition is true), and repeat-while / do-while (the do-while analogue, with do keyword). The conventional Kotlin discipline favours for-in over indexed loops; the substantial collection methods (map, filter, forEach, reduce, etc.) admit substantial functional-style transformations. The Sequence<T> interface admits lazy evaluation — substantial efficiency for pipelines on substantial collections. The combination — for-in as the principal form, the while/do-while for condition-driven loops, the substantial Iterable/Sequence method library, the break/continue with optional labels — is the substance of Kotlin’s iteration surface.

for-in

The principal iteration form:

for (item in collection) {
    use(item)
}

Examples:

val list = listOf(1, 2, 3, 4, 5)
for (x in list) {
    println(x)
}

// Range:
for (i in 0..9) {
    println(i)                                     // 0, 1, ..., 9
}

for (i in 0 until 10) {                            // exclusive: 0, 1, ..., 9
    println(i)
}

for (i in 10 downTo 1) {                           // 10, 9, ..., 1
    println(i)
}

for (i in 0..100 step 5) {                         // 0, 5, 10, ..., 100
    println(i)
}

// String:
for (c in "hello") {
    println(c)                                     // h, e, l, l, o
}

// Char range:
for (c in 'a'..'z') {
    print(c)
}

// Map:
val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {                        // destructuring
    println("$key: $value")
}

// Indices:
for (i in list.indices) {
    println("[$i] = ${list[i]}")
}

// With index and value:
for ((i, x) in list.withIndex()) {
    println("[$i] = $x")
}

The for-in admits any iterable — anything implementing Iterable<T> or providing an iterator() method.

while

var i = 0
while (i < 10) {
    println(i)
    i++
}

while (!done) {
    advance()
}

The condition must be a Boolean; parentheses around it are required.

do-while

The do-while form:

var input: String
do {
    input = readLine() ?: ""
} while (input.isEmpty())

The body runs at least once; the condition is checked at the end. The form is rare in idiomatic Kotlin; the while-with-break form is conventional.

break and continue

for (n in 0..100) {
    if (n > 50) break                              // exit loop
    if (n % 2 == 0) continue                      // skip to next iteration
    println(n)                                     // odd numbers
}

For labelled break/continue, the label syntax:

outer@ for (i in 1..10) {
    inner@ for (j in 1..10) {
        if (i * j > 50) break@outer                // breaks outer
        if (j == i) continue@inner                 // continues inner
    }
}

The label form is rare in idiomatic Kotlin — restructuring into a function with return is conventionally clearer.

Iterator-based iteration

Under the hood, for-in uses the Iterator<T>:

val list = listOf(1, 2, 3)
val iter = list.iterator()
while (iter.hasNext()) {
    println(iter.next())
}

The form is rarely used directly.

Collection methods

The conventional Kotlin discipline favours functional methods over explicit loops:

val list = listOf(1, 2, 3, 4, 5)

list.forEach { println(it) }                       // iterate
list.map { it * 2 }                                // transform
list.filter { it.isMultiple(of: 2) }              // wait, that's Swift
list.filter { it % 2 == 0 }                        // Kotlin
list.reduce { acc, x -> acc + x }                  // aggregate (no initial)
list.fold(0) { acc, x -> acc + x }                 // aggregate with initial
list.sum()
list.average()
list.count()
list.find { it > 3 }                               // first match (or null)
list.any { it < 0 }                                // bool: any match
list.all { it > 0 }                                // bool: all match
list.none { it > 100 }                             // bool: none match
list.minOrNull()
list.maxOrNull()
list.sorted()
list.sortedBy { it.toString().length }
list.reversed()
list.distinct()
list.take(3)
list.drop(2)
list.chunked(2)                                    // [[1,2], [3,4], [5]]
list.windowed(3)                                   // [[1,2,3], [2,3,4], [3,4,5]]
list.zip(otherList)                                // pairs

forEach

list.forEach { println(it) }
list.forEachIndexed { i, x -> println("[$i] = $x") }

forEach admits no early termination; for-in is conventional when break is needed.

map and friends

val doubled = list.map { it * 2 }
val strings = list.map { it.toString() }
val mapped = list.mapIndexed { i, x -> "[$i] $x" }

// flatMap:
val flat = listOf(listOf(1, 2), listOf(3, 4)).flatMap { it }   // [1,2,3,4]
val pairs = list.flatMap { listOf(it, it * 2) }                 // [1,2,2,4,3,6,...]

// mapNotNull:
val parsed = listOf("1", "two", "3").mapNotNull { it.toIntOrNull() }
                                                   // [1, 3]

filter and friends

val evens = list.filter { it % 2 == 0 }
val odds = list.filterNot { it % 2 == 0 }
val notNull = listOf(1, null, 2).filterNotNull()   // [1, 2]
val ints = listOf(1, "a", 2).filterIsInstance<Int>()  // [1, 2]

reduce and fold

val sum = list.reduce { a, b -> a + b }            // throws on empty
val sum = list.fold(0) { acc, x -> acc + x }       // safe with initial

// Build a map:
val counts = words.fold(mutableMapOf<String, Int>()) { acc, w ->
    acc[w] = (acc[w] ?: 0) + 1
    acc
}

// Or with associate / groupingBy:
val counts = words.groupingBy { it }.eachCount()

groupBy

val byParity = list.groupBy { if (it % 2 == 0) "even" else "odd" }
// {"odd" to [1, 3, 5], "even" to [2, 4]}

val byLength = words.groupBy { it.length }

partition

val (evens, odds) = list.partition { it % 2 == 0 }

associate

val byId = users.associateBy { it.id }
val nameToAge = users.associate { it.name to it.age }

Sequences for lazy iteration

For substantial chains of transformations, sequences admit “compute on demand”:

val result = (1..1_000_000).asSequence()
    .map { it * it }
    .filter { it % 3 == 0 }
    .take(10)
    .toList()

Without asSequence(), each intermediate produces a full list — substantial allocation. With asSequence(), items flow through the pipeline one at a time.

The generateSequence admits infinite sequences:

val fibonacci = generateSequence(0 to 1) { (a, b) -> b to (a + b) }
    .map { it.first }
    .take(10)
    .toList()                                      // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

val powers = generateSequence(1L) { it * 2 }
    .takeWhile { it < 1000 }
    .toList()                                      // [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

The sequences are lazy — admit substantial efficiency.

Range iteration

for (i in 1..10) print(i)                          // 1 to 10
for (i in 1 until 10) print(i)                     // 1 to 9
for (i in 1..<10) print(i)                         // 1 to 9 (since Kotlin 1.9)
for (i in 10 downTo 1) print(i)                    // 10 to 1
for (i in 1..100 step 5) print(i)                  // 1, 6, 11, ..., 96
for (c in 'a'..'z') print(c)
for (i in (1..10).reversed()) print(i)             // 10 to 1

Common patterns

Iterate with index

for ((i, x) in list.withIndex()) {
    println("[$i] = $x")
}

// Or:
list.forEachIndexed { i, x -> println("[$i] = $x") }

Iterate over map

val map = mapOf("a" to 1, "b" to 2)

for ((key, value) in map) {
    println("$key: $value")
}

map.forEach { (key, value) -> println("$key: $value") }
map.forEach { entry -> println("${entry.key}: ${entry.value}") }

// Sorted:
for ((key, value) in map.toSortedMap()) {
    println("$key: $value")
}

// Or:
map.entries.sortedBy { it.key }.forEach { (k, v) -> println("$k: $v") }

Functional pipeline

val result = users
    .filter { it.isActive }
    .map { it.email }
    .filter { it.endsWith("@example.com") }
    .sorted()
    .take(10)

Counting

val counts = mutableMapOf<String, Int>()
for (word in words) {
    counts[word] = (counts[word] ?: 0) + 1
}

// Or with reduce:
val counts = words.fold(mutableMapOf<String, Int>()) { acc, w ->
    acc.also { it[w] = (it[w] ?: 0) + 1 }
}

// Or with groupingBy:
val counts = words.groupingBy { it }.eachCount()

Top N

val top3 = items.sortedByDescending { it.score }.take(3)

// Or:
val top3 = items.sortedWith(compareByDescending<Item> { it.score }).take(3)

Group by

val byCategory = items.groupBy { it.category }
val byAgeRange = users.groupBy {
    when {
        it.age < 18 -> "minor"
        it.age < 65 -> "adult"
        else -> "senior"
    }
}

Sum/max/min

val sum = list.sum()
val total = items.sumOf { it.amount }
val max = list.maxOrNull() ?: 0
val youngest = users.minByOrNull { it.age }

Lazy infinite sequence

val primes = generateSequence(2) { it + 1 }
    .filter { n -> (2 until n).none { n % it == 0 } }
    .take(100)
    .toList()

Modifying during iteration

Modifying a collection during iteration with for-in admits ConcurrentModificationException. The conventional defences:

// Index-based:
for (i in list.indices) {
    if (shouldUpdate(list[i])) {
        list[i] = updated(list[i])                 // works on MutableList
    }
}

// removeIf:
mutableList.removeAll { it.isExpired }
mutableMap.entries.removeIf { it.value < 0 }

// Build a new collection:
val updated = list.map { if (shouldUpdate(it)) updated(it) else it }

Reading lines from stdin

generateSequence(::readLine).forEach { line ->
    process(line)
}

// Or:
while (true) {
    val line = readLine() ?: break
    process(line)
}

Iterating async with coroutines

suspend fun process() {
    flow.collect { item ->
        process(item)
    }
}

// Or with Flow operators:
flow
    .map { transform(it) }
    .filter { it.isValid }
    .collect { handle(it) }

Treated in Coroutines.

Sliding window

val prices = listOf(100, 102, 98, 105, 110, 108)
val movingAvg = prices.windowed(3).map { it.average() }
// [100.0, 101.67, 104.33, 107.67]

Chunked processing

records.chunked(100).forEach { batch ->
    processBatch(batch)
}

Zip for parallel iteration

val names = listOf("Alice", "Bob", "Charlie")
val ages = listOf(30, 25, 35)

names.zip(ages).forEach { (name, age) ->
    println("$name: $age")
}

// Or:
for ((name, age) in names.zip(ages)) {
    println("$name: $age")
}

Custom iteration

class CountDown(val from: Int) : Iterable<Int> {
    override fun iterator(): Iterator<Int> = object : Iterator<Int> {
        private var current = from
        override fun hasNext(): Boolean = current > 0
        override fun next(): Int {
            val result = current
            current--
            return result
        }
    }
}

for (n in CountDown(5)) {
    print(n)                                       // 54321
}

repeat

The repeat admits a numbered loop:

repeat(5) {
    println("hello")                               // 5 times
}

repeat(10) { i ->
    println(i)                                     // 0 to 9
}

A note on the conventional discipline

The contemporary Kotlin loops advice:

  • Use for-in for the conventional iteration.
  • Use ranges (.., until, ..<, downTo, step) for numeric iteration.
  • Use withIndex() or forEachIndexed for index access.
  • Use functional methods (map, filter, reduce) for transformations.
  • Use asSequence() for substantial chains on substantial collections.
  • Use generateSequence for infinite or generator-style sequences.
  • Use repeat(n) for numbered repetition.
  • Use forEach sparingly — for-in admits early break.
  • Use chunked / windowed for batch and window operations.
  • Use groupBy / groupingBy.eachCount() for substantial grouping.
  • Use partition for binary classification.
  • Avoid mutating collections during iteration — use index-based, removeAll, or build new.
  • Use labels rarely — restructure with functions.

The combination — for-in over Iterable/Sequence, the substantial range surface, the conventional functional methods, lazy sequences for substantial chains, the repeat/generateSequence for substantial generator patterns — is the substance of Kotlin’s iteration surface. The discipline produces concise, expressive iteration code with substantial functional-style flexibility.