Control Flow (if, for, while)

Reviewed & published by Brayan K

Control flow is how a program decides what to do and how often to do it — Kotlin's if, for, and while let you branch on conditions and repeat work.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

A standout twist: in Kotlin if is an expression that returns a value, so it replaces the ternary operator entirely.

What You'll Learn in This Lesson

1️⃣ if/else — Statement and Expression

You can use if the familiar way — to run one block or another. But Kotlin goes further: if is an expression, so it produces a value you can assign. The value is the last expression of whichever branch runs. That is why Kotlin has no ? : ternary — if/else already covers it.

fun main() {
    val a = 7
    val b = 12

    // Used as a STATEMENT (classic if/else):
    if (a > b) {
        println("a is bigger")
    } else {
        println("b is bigger")        // -> b is bigger
    }

    // Used as an EXPRESSION — if RETURNS a value in Kotlin.
    // The last line of each branch is the result.
    val max = if (a > b) a else b
    println("max = $max")             // -> max = 12

    // There is no ternary (?:) in Kotlin — if/else IS the ternary.
    val label = if (a % 2 == 0) "even" else "odd"
    println("$a is $label")           // -> 7 is odd
}

2️⃣ for Loops — Ranges, Collections, Indices

Kotlin's for loop always iterates over something: a range like 1..3, a collection like a list, or an indexed view via withIndex(). There is no C-style for (i = 0; i < n; i++) — ranges replace it and read far more clearly.

fun main() {
    // Iterate over a RANGE (1, 2, 3):
    for (i in 1..3) {
        println("count $i")
    }

    // Iterate over a COLLECTION directly:
    val fruits = listOf("apple", "pear", "plum")
    for (fruit in fruits) {
        println(fruit)
    }

    // Iterate with INDEX + value using indices / withIndex:
    for ((index, fruit) in fruits.withIndex()) {
        println("$index -> $fruit")
    }
}

3️⃣ while, do/while, break, and continue

A while loop repeats as long as its condition is true, checking before each pass. A do/while loop runs the body once and checks after. Inside any loop, break exits immediately and continue jumps to the next pass.

fun main() {
    // while: checks the condition BEFORE each pass.
    var n = 3
    while (n > 0) {
        println("n = $n")
        n--
    }

    // do/while: runs the body ONCE, then checks.
    var k = 0
    do {
        println("k = $k")
        k++
    } while (k < 2)
}

Now the same loop-control tools, plus a label to break out of an outer loop from deep inside a nested one:

fun main() {
    // break stops the loop; continue skips to the next pass.
    for (i in 1..5) {
        if (i == 3) continue   // skip 3
        if (i == 5) break      // stop before 5
        println(i)
    }

    // Labels let you break an OUTER loop from inside an inner one.
    outer@ for (row in 1..3) {
        for (col in 1..3) {
            if (row + col == 4) break@outer
            println("$row,$col")
        }
    }
}

Your turn. Fill in the ___ blanks, then run and compare.

// 🎯 YOUR TURN — replace each ___ then run main()
fun main() {
    val score = 85

    // 1) if as an EXPRESSION: "pass" if score >= 60, else "fail"
    val result = if (score >= 60) "pass" else ___   // 👉 "fail"

    // 2) loop the numbers 1 through 3 with a range
    for (i in 1.._) {                                // 👉 1..3
        print(i)
    }
    println()

    println(result)

    // ✅ Expected:
    // 123
    // pass
}

Mini-Challenge: FizzBuzz

The legendary interview warm-up. Combine a for loop, if/else, and the modulo operator %.

// 🎯 MINI-CHALLENGE: FizzBuzz (the classic).
// Save as FizzBuzz.kt and run main().
//
// Loop i from 1 to 5. For each number:
//   • print "Fizz"     if i is divisible by 3
//   • print "Buzz"     if i is divisible by 5
//   • print "FizzBuzz" if divisible by BOTH 3 and 5
//   • otherwise print the number itself
//
// Hint: use if/else if/else and the % operator.
//
// ✅ Expected output:
//    1
//    2
//    Fizz
//    4
//    Buzz

fun main() {
    for (i in 1..5) {
        // your code here
    }
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Control Flow

ConstructKotlin Syntax
if statementif (c) { ... } else { ... }
if expressionval m = if (c) a else b
for over rangefor (i in 1..3) { ... }
for with indexfor ((i, v) in list.withIndex())
whilewhile (c) { ... }
do/whiledo { ... } while (c)
labeled breakbreak@outer

🎉 Lesson Complete!

Practice quiz

In Kotlin, can if be used as an expression that returns a value?

  • No, if is always a statement
  • Only inside loops
  • Yes, if/else returns the last expression of the chosen branch
  • Only with a return keyword

Answer: Yes, if/else returns the last expression of the chosen branch. if is an expression in Kotlin, so val max = if (a > b) a else b works.

Why does Kotlin have no ternary (cond ? a : b) operator?

  • Because if/else is already an expression that returns a value
  • Because ternary is considered unsafe
  • Because when replaces it
  • It does have a ternary operator

Answer: Because if/else is already an expression that returns a value. if/else is an expression, so it covers what a ternary does in other languages.

When if is used as an expression assigned to a variable, the else branch is:

  • Optional
  • Forbidden
  • Replaced by when
  • Required so every path produces a value

Answer: Required so every path produces a value. The compiler must know what value to produce in every case, so else is required.

What does the range 1..3 produce when looped?

  • 1, 2 (upper excluded)
  • 1, 2, 3 (both endpoints included)
  • 0, 1, 2
  • 2, 3

Answer: 1, 2, 3 (both endpoints included). The .. operator builds a closed range that includes both endpoints.

What does 1 until 3 produce?

  • 1, 2 (upper bound excluded)
  • 1, 2, 3
  • 0, 1, 2, 3
  • 2, 3

Answer: 1, 2 (upper bound excluded). until builds a half-open range that excludes the upper bound.

Which loop checks its condition AFTER running the body once?

  • while
  • for
  • do/while
  • repeat

Answer: do/while. do/while runs the body once, then checks the condition.

Inside a loop, what does continue do?

  • Exits the loop entirely
  • Skips to the next iteration
  • Restarts the whole loop
  • Pauses execution

Answer: Skips to the next iteration. continue jumps to the next pass; break exits the loop.

How do you break out of an OUTER loop from inside a nested loop?

  • Use break twice
  • Use return
  • It is not possible
  • Use a label, e.g. break@outer

Answer: Use a label, e.g. break@outer. A label like outer@ lets you write break@outer to exit the outer loop.

Which is a valid Kotlin for loop over a list named fruits?

  • for (i = 0; i < fruits.size; i++) { }
  • for (fruit in fruits) { }
  • foreach fruit in fruits { }
  • for fruit of fruits { }

Answer: for (fruit in fruits) { }. Kotlin's for always iterates over something; there is no C-style for.

What does fruits.withIndex() give you in a for loop?

  • Only the indices
  • Only the values reversed
  • Both the index and the value of each element
  • A new sorted list

Answer: Both the index and the value of each element. withIndex() yields (index, value) pairs you can destructure.

Continue this course

Frequently asked questions

Why does Kotlin have no ternary operator (cond ? a : b)?

Because it doesn't need one. In Kotlin if/else is an expression, meaning it evaluates to a value, so val max = if (a > b) a else b does exactly what a ternary does in other languages — only more readable. The result of each branch is its last expression. The same idea applies to when, which is Kotlin's powerful replacement for switch and also returns a value.

What is the difference between a range like 1..3 and 1 until 3?

The .. operator builds a closed range that INCLUDES both endpoints, so 1..3 is 1, 2, 3. The until keyword builds a half-open range that EXCLUDES the upper bound, so 1 until 3 is just 1, 2. until is handy when looping over indices, where you usually want 0 until size. You'll explore ranges and progressions in depth in the next lesson.

When should I use while versus for?

Use a for loop when you know what you are iterating over — a range, a list, or a sequence of indices. Use while when you loop until some condition changes and you don't have a fixed collection, for example reading input until a sentinel value appears. Use do/while in the rare case where the body must run at least once before the condition is checked.

What are labels like outer@ used for with break and continue?

By default break and continue affect only the innermost loop that contains them. A label such as outer@ placed before a loop lets you write break@outer or continue@outer from inside a nested loop to control the outer loop directly. This avoids awkward flag variables when you need to exit several levels of nesting at once.