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-infor the conventional iteration. - Use ranges (
..,until,..<,downTo,step) for numeric iteration. - Use
withIndex()orforEachIndexedfor index access. - Use functional methods (
map,filter,reduce) for transformations. - Use
asSequence()for substantial chains on substantial collections. - Use
generateSequencefor infinite or generator-style sequences. - Use
repeat(n)for numbered repetition. - Use
forEachsparingly —for-inadmits early break. - Use
chunked/windowedfor batch and window operations. - Use
groupBy/groupingBy.eachCount()for substantial grouping. - Use
partitionfor 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.