Kotlin Help

Conditions and loops

Kotlin gives you flexible tools to control your program's flow. Use if, when, and loops to define clear, expressive logic for your conditions.

If expression

To use if in Kotlin, add the condition to check within parentheses () and the action to take if the result is true within curly braces {}. You can use else and else if for additional branches and checks.

You can also write if as an expression, which lets you assign its returned value directly to a variable. In this form, an else branch is required. The if expression serves the same purpose as the ternary operator (condition ? then : else) found in other languages.

For example:

fun main() { val heightAlice = 160 val heightBob = 175 //sampleStart var taller = heightAlice if (heightAlice < heightBob) taller = heightBob // Uses an else branch if (heightAlice > heightBob) { taller = heightAlice } else { taller = heightBob } // Uses if as an expression taller = if (heightAlice > heightBob) heightAlice else heightBob // Uses else if as an expression: val heightLimit = 150 val heightOrLimit = if (heightLimit > heightAlice) heightLimit else if (heightAlice > heightBob) heightAlice else heightBob println("Taller height is $taller") // Taller height is 175 println("Height or limit is $heightOrLimit") // Height or limit is 175 //sampleEnd }

Each branch in an if expression can be a block, where the value of the last expression becomes the result:

fun main() { //sampleStart val heightAlice = 160 val heightBob = 175 val taller = if (heightAlice > heightBob) { print("Choose Alice\n") heightAlice } else { print("Choose Bob\n") heightBob } println("Taller height is $taller") //sampleEnd }

When expressions and statements

when is a conditional expression that runs code based on multiple possible values or conditions. It's similar to the switch statement in Java, C, and other languages. when evaluates its argument and compares the result against each branch in order until one branch condition is satisfied. For example:

fun main() { //sampleStart val userRole = "Editor" when (userRole) { "Viewer" -> print("User has read-only access") "Editor" -> print("User can edit content") else -> print("User role is not recognized") } // User can edit content //sampleEnd }

You can use when either as an expression or a statement. As an expression, when returns a value you can use later in your code. As a statement, when completes an action without returning a result:

Expression

Statement

// Returns a string assigned to the // text variable val text = when (x) { 1 -> "x == 1" 2 -> "x == 2" else -> "x is neither 1 nor 2" }
// Returns no result but triggers a // print statement when (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> print("x is neither 1 nor 2") }

Secondly, you can use when with or without a subject. The behavior stays the same either way. Using a subject usually makes your code more readable and maintainable because it clearly shows what you're checking.

With subject x

Without subject

when(x) { ... }
when { ... }

How you use when determines whether you need to cover all possible cases in your branches. Covering all possible cases is called being exhaustive.

Statements

If you use when as a statement, you don't need to cover all possible cases. In this example, some cases aren't covered, so no branch is triggered. However, no error occurs:

fun main() { //sampleStart val deliveryStatus = "OutForDelivery" when (deliveryStatus) { // Not all cases are covered "Pending" -> print("Your order is being prepared") "Shipped" -> print("Your order is on the way") } //sampleEnd }

Just like with if, each branch can be a block, and its value is the value of the last expression in the block.

Expressions

If you use when as an expression, you must cover all possible cases. The value of the first matching branch becomes the value of the overall expression. If you don't cover all cases, the compiler throws an error.

If your when expression has a subject, you can use an else branch to make sure that all possible cases are covered, but it isn't mandatory. For example, if your subject is a Boolean, enum class, sealed class, or one of their nullable counterparts, you can cover all cases without an else branch:

import kotlin.random.Random //sampleStart enum class Bit { ZERO, ONE } fun getRandomBit(): Bit { return if (Random.nextBoolean()) Bit.ONE else Bit.ZERO } fun main() { val numericValue = when (getRandomBit()) { // No else branch is needed because all cases are covered Bit.ZERO -> 0 Bit.ONE -> 1 } println("Random bit as number: $numericValue") // Random bit as number: 0 //sampleEnd }

If your when expression doesn't have a subject, you must have an else branch or the compiler throws an error. The else branch is evaluated when none of the other branch conditions are satisfied:

fun main() { //sampleStart val localFileSize = 1200 val remoteFileSize = 1200 val message = when { localFileSize > remoteFileSize -> "Local file is larger than remote file" localFileSize < remoteFileSize -> "Local file is smaller than remote file" else -> "Local and remote files are the same size" } println(message) // Local and remote files are the same size //sampleEnd }

Other ways to use when

when expressions and statements offer different ways to simplify your code, handle multiple conditions, and perform type checks.

Group multiple conditions into a single branch using commas:

fun main() { val ticketPriority = "High" //sampleStart when (ticketPriority) { "Low", "Medium" -> print("Standard response time") else -> print("High-priority handling") } //sampleEnd }

Use expressions that evaluate to true or false as branch conditions:

fun main() { val storedPin = "1234" val enteredPin = 1234 //sampleStart when (enteredPin) { // Expression storedPin.toInt() -> print("PIN is correct") else -> print("Incorrect PIN") } //sampleEnd }

Check whether a value is or isn't contained in a range or collection using the in or !in keywords:

fun main() { val x = 7 val validNumbers = setOf(15, 16, 17) //sampleStart when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") !in 10..20 -> print("x is outside the range") else -> print("none of the above") } //sampleEnd }

Check a value's type using the is or !is keywords. Due to smart casts, you can access the member functions and properties of the type directly:

fun hasPrefix(input: Any): Boolean = when (input) { is String -> input.startsWith("ID-") else -> false } fun main() { val testInput = "ID-98345" println(hasPrefix(testInput)) // true }

Use when instead of a traditional if-else if chain. Without a subject, the branch conditions are simply boolean expressions. The first branch with a true condition runs:

fun Int.isOdd() = this % 2 != 0 fun Int.isEven() = this % 2 == 0 fun main() { //sampleStart val x = 5 val y = 8 when { x.isOdd() -> print("x is odd") y.isEven() -> print("y is even") else -> print("x+y is odd") } // x is odd //sampleEnd }

Finally, capture the subject in a variable by using the following syntax:

fun main() { val message = when (val input = "yes") { "yes" -> "You said yes" "no" -> "You said no" else -> "Unrecognized input: $input" } println(message) // You said yes }

The scope of a variable introduced as the subject is restricted to the body of the when expression or statement.

Guard conditions

Guard conditions allow you to include more than one condition to the branches of a when expression or statement, making complex control flow more explicit and concise. You can use guard conditions with when as long as it has a subject.

Place a guard condition after the primary condition in the same branch, separated by if:

sealed interface Animal { data class Cat(val mouseHunter: Boolean) : Animal data class Dog(val breed: String) : Animal } fun feedDog() = println("Feeding a dog") fun feedCat() = println("Feeding a cat") //sampleStart fun feedAnimal(animal: Animal) { when (animal) { // Branch with only primary condition // Calls feedDog() when animal is Dog is Animal.Dog -> feedDog() // Branch with both primary and guard conditions // Calls feedCat() when animal is Cat and not mouseHunter is Animal.Cat if !animal.mouseHunter -> feedCat() // Prints "Unknown animal" if none of the above conditions match else -> println("Unknown animal") } } fun main() { val animals = listOf( Animal.Dog("Beagle"), Animal.Cat(mouseHunter = false), Animal.Cat(mouseHunter = true) ) animals.forEach { feedAnimal(it) } // Feeding a dog // Feeding a cat // Unknown animal } //sampleEnd

You can't use guard conditions when you have multiple conditions separated by a comma. For example:

0, 1 -> print("x == 0 or x == 1")

In a single when expression or statement, you can combine branches with and without guard conditions. The code in a branch with a guard condition runs only if both the primary condition and the guard condition evaluate to true. If the primary condition doesn't match, the guard condition isn't evaluated.

Since when statements don't need to cover all cases, using guard conditions in when statements without an else branch means that if no conditions match, no code is run.

Unlike statements, when expressions must cover all cases. If you use guard conditions in when expressions without an else branch, the compiler requires you to handle every possible case to avoid runtime errors.

Combine multiple guard conditions within a single branch using the boolean operators && (AND) or || (OR). Use parentheses around the boolean expressions to avoid confusion:

when (animal) { is Animal.Cat if (!animal.mouseHunter && animal.hungry) -> feedCat() }

Guard conditions also support else if:

when (animal) { // Checks if `animal` is `Dog` is Animal.Dog -> feedDog() // Guard condition that checks if `animal` is `Cat` and not `mouseHunter` is Animal.Cat if !animal.mouseHunter -> feedCat() // Calls giveLettuce() if none of the above conditions match and animal.eatsPlants is true else if animal.eatsPlants -> giveLettuce() // Prints "Unknown animal" if none of the above conditions match else -> println("Unknown animal") }

For loops

Use the for loop to iterate through a collection, array, or range:

for (item in collection) print(item)

The body of a for loop can be a block with curly braces {}.

fun main() { val shoppingList = listOf("Milk", "Bananas", "Bread") //sampleStart println("Things to buy:") for (item in shoppingList) { println("- $item") } // Things to buy: // - Milk // - Bananas // - Bread //sampleEnd }

Ranges

To iterate over a range of numbers, use a range expression with .. and ..< operators:

fun main() { //sampleStart println("Closed-ended range:") for (i in 1..6) { print(i) } // Closed-ended range: // 123456 println("\nOpen-ended range:") for (i in 1..<6) { print(i) } // Open-ended range: // 12345 println("\nReverse order in steps of 2:") for (i in 6 downTo 0 step 2) { print(i) } // Reverse order in steps of 2: // 6420 //sampleEnd }

Arrays

If you want to iterate through an array or a list with an index, you can use the indices property:

fun main() { val routineSteps = arrayOf("Wake up", "Brush teeth", "Make coffee") //sampleStart for (i in routineSteps.indices) { println(routineSteps[i]) } // Wake up // Brush teeth // Make coffee //sampleEnd }

Alternatively, you can use the .withIndex() function from the standard library:

fun main() { val routineSteps = arrayOf("Wake up", "Brush teeth", "Make coffee") //sampleStart for ((index, value) in routineSteps.withIndex()) { println("The step at $index is \"$value\"") } // The step at 0 is "Wake up" // The step at 1 is "Brush teeth" // The step at 2 is "Make coffee" //sampleEnd }

Iterators

The for loop iterates through anything that provides an iterator. Collections provide iterators by default, whereas ranges and arrays are compiled into index-based loops.

You can create your own iterators by providing a member or extension function called iterator() that returns an Iterator<>. The iterator() function must have a next() function and a hasNext() function that returns a Boolean.

The easiest way to create your own iterator for a class is to inherit from the Iterable<T> interface and override the iterator(), next(), and hasNext() functions that are already there. For example:

class Booklet(val totalPages: Int) : Iterable<Int> { override fun iterator(): Iterator<Int> { return object : Iterator<Int> { var current = 1 override fun hasNext() = current <= totalPages override fun next() = current++ } } } fun main() { val booklet = Booklet(3) for (page in booklet) { println("Reading page $page") } // Reading page 1 // Reading page 2 // Reading page 3 }

Alternatively, you can create the functions from scratch. In this case, add the operator keyword to the functions:

//sampleStart class Booklet(val totalPages: Int) { operator fun iterator(): Iterator<Int> { return object { var current = 1 operator fun hasNext() = current <= totalPages operator fun next() = current++ }.let { object : Iterator<Int> { override fun hasNext() = it.hasNext() override fun next() = it.next() } } } } //sampleEnd fun main() { val booklet = Booklet(3) for (page in booklet) { println("Reading page $page") } // Reading page 1 // Reading page 2 // Reading page 3 }

While loops

while and do-while loops run the code in their body continuously while the condition is satisfied. The difference between them is the condition checking time:

  • while checks the condition and, if it's satisfied, runs the code in its body and then returns to the condition check.

  • do-while runs the code in its body and then checks the condition. If it's satisfied, the loop repeats. So, the body of do-while runs at least once, regardless of the condition.

For a while loop, place the condition to check in parentheses () and the body within curly braces {}:

fun main() { var carsInGarage = 0 val maxCapacity = 3 //sampleStart while (carsInGarage < maxCapacity) { println("Car entered. Cars now in garage: ${++carsInGarage}") } // Car entered. Cars now in garage: 1 // Car entered. Cars now in garage: 2 // Car entered. Cars now in garage: 3 println("Garage is full!") // Garage is full! //sampleEnd }

For a do-while loop, place the body within curly braces {} first before the condition to check in parentheses ():

import kotlin.random.Random fun main() { var roll: Int //sampleStart do { roll = Random.nextInt(1, 7) println("Rolled a $roll") } while (roll != 6) // Rolled a 2 // Rolled a 6 println("Got a 6! Game over.") // Got a 6! Game over. //sampleEnd }

Break and continue in loops

Kotlin supports traditional break and continue operators in loops. See Returns and jumps.

05 August 2025