In Java and other older languages, a for-loop typically has three parts: the first initializes the variable before the loop starts; the second contains the condition for the execution of the code block; the third is executed after the code block.
// Java
for(int i=0; i < 5; i++){
System.out.println(i);
}
However, this is considered complicated and error-prone. Just consider a situation in which someone uses > or <= instead of <. Such a small difference is not easy to notice, but it essentially influences the behavior of this for-loop.
As an alternative to this classic for-loop, many languages have introduced a modern alternative for iterating over collections. This is why, in languages like Java or JavaScript, there are two completely different kinds of for-loops, both of which are defined with the same for keyword. Kotlin has simplified this. In Kotlin, we have one universal for-loop that can be expressively used to iterate over a collection, a map, a range of numbers, and much more.
In general, a for-loop is used in Kotlin to iterate over something that is iterable[^07_1].
We can iterate over lists or sets.
fun main() {
val list = listOf("A", "B", "C")
for (letter in list) {
print(letter)
}
// Variable type can be explicit
for (str: String in setOf("D", "E", "F")) {
print(str)
}
}
// ABCDEF
We can also iterate over any other object as long as it contains the iterator method with no parameters, plus the Iterator result type and the operator modifier. The easiest way to define this operator method is to make your class implement the Iterable interface (then you do not need to define operator modifier yourself, as it is inherited from Iterable). In the below example, we define a Tree class that implements Iterable<String>, so we must override the iterator method, and we can iterate over an instance of this class.
fun main() {
val tree = Tree(
value = "B",
left = Tree("A"),
right = Tree("D", left = Tree("C"))
)
for (value in tree) {
print(value) // ABCD
}
}
class Tree(
val value: String,
val left: Tree? = null,
val right: Tree? = null,
) : Iterable<String> {
override fun iterator(): Iterator<String> = iterator {
if (left != null) yieldAll(left)
yield(value)
if (right != null) yieldAll(right)
}
}
The inferred variable type of the variable defined inside the for-loop comes from the Iterable type argument. When we iterate over Iterable<User>, the inferred element type will be User. When we iterate over Iterable<Long?>, the inferred element type will be Long?. The same applies to all other types.
This mechanism, which relies on Iterable, is really powerful and allows us to cover numerous use cases, one of the most notable of which is the use of ranges to express progressions.
Ranges
In Kotlin, if you place two dots between two numbers, like 1..5, you create an IntRange. This class implements Iterable<Int>, so we can use it in a for-loop:
fun main() {
for (i in 1..5) {
print(i)
}
}
// 12345
This solution is efficient as well as convenient because the Kotlin compiler optimizes its performance under the hood.
Ranges created with .. include the last value (which means they are closed ranges). If you want a range that stops before the last value, use the ..< operator or until infix function instead.
fun main() {
for (i in 1..<5) {
print(i)
}
}
// 1234
fun main() {
for (i in 1 until 5) {
print(i)
}
}
// 1234
Both .. and ..< start with the value on their left and progress toward the right number in increments of one. If you use a bigger number on the left, the result is an empty range.
fun main() {
for (i in 5..1) {
print(i)
}
for (i in 5..<1) {
print(i)
}
}
// (nothing is printed)
If you want to iterate in the other direction, from larger to smaller numbers, use the downTo function.
fun main() {
for (i in 5 downTo 1) {
print(i)
}
}
// 54321
The default step in all those cases is 1. If you want to use a different step, you should use the step infix function.
fun main() {
for (i in 1..10 step 3) {
print("$i ")
}
println()
for (i in 1..<10 step 3) {
print("$i ")
}
println()
for (i in 10 downTo 1 step 3) {
print("$i ")
}
}
// 1 4 7 10
// 1 4 7
// 10 7 4 1
Break and continue
Inside loops, we can use the break and continue keywords:
break - terminates the nearest enclosing loop.
continue - proceeds to the next step of the nearest enclosing loop.
fun main() {
for (i in 1..5) {
if (i == 3) break
print(i)
}
// 12
println()
for (i in 1..5) {
if (i == 3) continue
print(i)
}
// 1245
}
Both are used rather rarely, and I had trouble finding even one real-life example in the commercial projects I have co-created. I also assume that they are well-known to developers who’ve come to Kotlin from older languages. This is why I present these keywords so briefly.
Use cases
Developers with experience in older languages often use a for-loop where slightly more-modern alternatives should be used instead. For instance, in some projects I can find a for-loop that is used to iterate over elements with indices.
fun main() {
val names = listOf("Alex", "Bob", "Celina")
for (i in 0..<names.size) {
val name = names[i]
println("[$i] $name")
}
}
// [0] Alex
// [1] Bob
// [2] Celina
This is not a good solution. There are multiple ways to do this better in Kotlin.
First, instead of explicitly iterating over a range 0..<names.size, we could use the indices property, which returns a range of available indices.
fun main() {
val names = listOf("Alex", "Bob", "Celina")
for (i in names.indices) {
val name = names[i]
println("[$i] $name")
}
}
// [0] Alex
// [1] Bob
// [2] Celina
Second, instead of iterating over indices and finding an element for each of them, we could instead iterate over indexed values. We can create indexed values using withIndex on iterable. Each indexed value includes both an index and a value. Such objects can be destructured in a for-loop[^07_2].
fun main() {
val names = listOf("Alex", "Bob", "Celina")
for ((i, name) in names.withIndex()) {
println("[$i] $name")
}
}
// [0] Alex
// [1] Bob
// [2] Celina
Third, an even better solution is to use forEachIndexed, which is explained in the next book: Functional Kotlin.
fun main() {
val names = listOf("Alex", "Bob", "Celina")
names.forEachIndexed { i, name ->
println("[$i] $name")
}
}
// [0] Alex
// [1] Bob
// [2] Celina
Another popular use case is iterating over a map. Developers with a Java background often do it this way:
fun main() {
val capitals = mapOf(
"USA" to "Washington DC",
"Poland" to "Warsaw",
"Ukraine" to "Kiev"
)
for (entry in capitals.entries) {
println("The capital of ${entry.key} is ${entry.value}")
}
}
// The capital of USA is Washington DC
// The capital of Poland is Warsaw
// The capital of Ukraine is Kiev
This can be improved by directly iterating over a map, so calling entries is unnecessary. Also, we can destructure entries to better name the values.
fun main() {
val capitals = mapOf(
"USA" to "Washington DC",
"Poland" to "Warsaw",
"Ukraine" to "Kiev"
)
for ((country, capital) in capitals) {
println("The capital of $country is $capital")
}
}
// The capital of USA is Washington DC
// The capital of Poland is Warsaw
// The capital of Ukraine is Kiev
We can use forEach for a map.
fun main() {
val capitals = mapOf(
"USA" to "Washington DC",
"Poland" to "Warsaw",
"Ukraine" to "Kiev"
)
capitals.forEach { (country, capital) ->
println("The capital of $country is $capital")
}
}
// The capital of USA is Washington DC
// The capital of Poland is Warsaw
// The capital of Ukraine is Kiev
Summary
In this chapter, we've learned about using the for-loop. It is really simple and powerful in Kotlin, so it’s worth knowing how it works, even though it’s not used very often (due to Kotlin’s amazing functional features, which are often used instead).
Now, let's talk about one of the most important Kotlin improvements over Java: good support for handling nullability.
[^07_1]: Has the iterator operator method.
[^07_2]: Destructuring will be explained in more depth in the Data classes chapter.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
Owen has been developing software since the mid 1990s and remembers the productivity of languages such as Clipper and Borland Delphi.
Since 2001, He moved to Web, Server based Java and the Open Source revolution.
With many years of commercial Java experience, He picked up on Kotlin in early 2015.
After taking detours into Clojure and Scala, like Goldilocks, He thinks Kotlin is just right and tastes the best.
Owen enthusiastically helps Kotlin developers continue to succeed.
Nicola Corti is a Google Developer Expert for Kotlin. He has been working with the language since before version 1.0 and he is the maintainer of several open-source libraries and tools.
He's currently working as Android Infrastructure Engineer at Spotify in Stockholm, Sweden.
Furthermore, he is an active member of the developer community.
His involvement goes from speaking at international conferences about Mobile development to leading communities across Europe (GDG Pisa, KUG Hamburg, GDG Sthlm Android).
In his free time, he also loves baking, photography, and running.
Software architect with 15 years of experience, currently working on building infrastructure for AI. I think Kotlin is one of the best programming languages ever created.
Emanuele is passionate about Android and has been fascinated by it since 2010: the more he learns, the more he wishes to share what he knows with others, which is why he started maintaining his own blog.
In his current role as Senior Android Developer at Mozio, he is now focusing on Kotlin Multiplatform Mobile: he has already given a couple of talks on this topic on various occasions, so far.
Interested in everything Android and Kotlin related, including architecture patterns, TDD, functional programming and Jetpack Compose. Author of several articles about Android and Kotlin Coroutines.