skip to content

Control Flow

Branching and looping in Kotlin, where most constructs are expressions rather than statements: if, when with its exhaustiveness rules, for over ranges and iterables, and labeled jumps. Interviewers use this group to check that you have stopped reaching for switch and the ternary operator.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

Kotlin has no C-style three-part for loop (for (int i=0; i<n; i++)). How do you write the equivalent loops in Kotlin, and what does the for loop actually require to iterate?

level: juniorimportance: must knowfreq 70%

answer

  1. for = foreach, needs iterator()
  2. 0 until n replaces i<n
  3. 1..n inclusive, until exclusive
  4. downTo / step for direction & stride
  5. irregular update -> while

basics

~10 s

Kotlin's for loop walks over a sequence, not a counter. To count, you loop over a range like 0 until n. For anything else, for works on anything you can iterate, like a list.

solid answer

~40 s

Kotlin deliberately drops the C-style for(init; cond; step) form. Instead, for (x in something) iterates over anything that provides an iterator() function (a member or extension). To count, you build a range and iterate it: for (i in 0 until n) replaces the classic i=0; i<n; i++. For other directions/strides use downTo and step. The equivalent of a manual counter loop is just for (i in 0 until list.size), but idiomatically you iterate the collection directly: for (item in list). When you need both the index and the value, use for ((index, value) in list.withIndex()). If you genuinely need arbitrary update logic, fall back to a while loop. The key idea: Kotlin for is a foreach over an Iterable/range, never a three-part counter.

code

kotlin · 11 lines
kotlin
// Java: for (int i = 0; i < n; i++)
for (i in 0 until n) print(i)

// Java: for (int i = n-1; i >= 0; i--)
for (i in n - 1 downTo 0) print(i)

// Idiomatic: iterate the collection itself
for (item in items) println(item)

// Custom iterable via extension iterator()
operator fun ClosedRange<MyDate>.iterator(): Iterator<MyDate> = TODO()

go deeper

for a junior

Knows for is foreach and uses 0 until n for index loops.

for a middle

Distinguishes until/../downTo/step correctly and reaches for while when stepping is irregular.

for a senior

Explains the iterator() operator contract and that any type providing it works in for, including extension iterators.

for a principal

Frames the design rationale (fewer off-by-one bugs, ranges as first-class objects) and the cost/zero-cost tradeoffs of range iteration.

## Why no C-style for In Java/C you write `for (int i = 0; i < n; i++)` — three parts: **init**, **condition**, **update**. Kotlin removed this form entirely to avoid off-by-one bugs and to make loops read as iteration over a sequence. ## What Kotlin's for requires The `for (x in expr)` loop works on any `expr` whose type provides an **`iterator()`** function returning something with `next()` and `hasNext()`. That function can be a **member** (collections, arrays, ranges, strings, maps) or even an **extension function** you declare yourself. So `for` is a *foreach*, not a counter. ## Counting with ranges To reproduce a counter you iterate a **range expression**: ```kotlin for (i in 0 until n) { /* i = 0,1,...,n-1 */ } // like i<n for (i in 1..n) { /* i = 1,2,...,n */ } // inclusive both ends for (i in n downTo 1){ /* counts down */ } for (i in 0 until n step 2) { /* stride of 2 */ } ``` - `1..10` — `rangeTo`, **inclusive** of both ends (1..10 = 1,2,...,10). - `0 until 10` — half-open, **excludes** the upper bound (0..9). Best match for index loops. - `10 downTo 1` — descending. - `step k` — changes the stride (must be positive even for downTo). ## Idiomatic iteration Usually you don't need indices at all: ```kotlin for (item in list) println(item) for (c in "hello") println(c) // String is iterable for ((k, v) in map) println("$k=$v") // destructuring map entries ``` ## When you really need arbitrary update If the update isn't a fixed stride (e.g. `i *= 2`), use a `while`: ```kotlin var i = 1 while (i < n) { i *= 2 } ``` ## Summary Kotlin `for` = foreach over anything with `iterator()`. Counters are expressed as ranges (`until`, `downTo`, `step`); irregular stepping uses `while`.

  • What's the difference between 0 until n and 0..n?
    0 until n is half-open and yields 0..n-1 (good for indices); 0..n is inclusive and yields 0..n, one extra element.
  • Can you make your own type usable in a for loop?
    Yes — declare an operator function iterator() (member or extension) returning an Iterator with next()/hasNext().

A C-style for is like manually turning an odometer; Kotlin's for is like walking down a pre-laid track — you describe the track (a range), not the steps.

saying these in an interview costs you the question

  • Claiming Kotlin has a three-part for loop
  • Using 1..n.size where 0 until size is meant (off-by-one)
  • Thinking for only works on lists/arrays, not custom types
  • Confusing until (exclusive) with .. (inclusive)

context

open as a page

Kotlin has no ternary operator (a ? b : c). How do you write the equivalent, and what does that tell you about if in Kotlin?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Kotlin has no a ? b : c. You use if/else directly, because if returns a value. Write val m = if (a > b) a else b. The if/else expression takes the place of the ternary.

open as a page

In a nested for loop, which loop does a plain `break` (without a label) terminate, and how do you instead break out of the outer loop?

level: juniorimportance: must knowfreq 70%

basics

~10 s

A plain break only stops the loop directly around it (the innermost one). To stop an outer loop, you put a name (label) on that loop and write break@name.

open as a page

In Kotlin, what does a bare `return` statement inside a lambda passed to `forEach` do, and how is that different from returning from the lambda alone?

level: juniorimportance: must knowfreq 70%

basics

~10 s

A bare return inside a forEach lambda exits the whole surrounding function, not just the loop step. To skip only the current step you write return@forEach, which leaves the lambda and continues the loop.

open as a page

In Kotlin, when must a `when` be exhaustive (cover all cases), and when can it leave cases out? Explain the difference between a `when` expression and a `when` statement.

level: juniorimportance: must knowfreq 70%

basics

~20 s

If you use when to produce a value (like assigning it to a variable or returning it), it must handle every possible case. If you use when just to do an action and ignore its value, it does not have to cover everything.

open as a page

What is Kotlin's `when` and how does it differ from a Java/C-style `switch`?

level: juniorimportance: must knowfreq 80%

basics

~10 s

when is Kotlin's replacement for switch. You list conditions and pick the first matching branch. It can be used as a value-producing expression or as a plain statement, and branches don't fall through.

open as a page

Explain the range-building expressions in Kotlin: 1..10, until, downTo, and step. What does each produce and what are the gotchas (e.g. step with downTo, empty ranges)?

level: middleimportance: must knowfreq 65%

basics

~10 s

1..10 counts 1 to 10 including both ends. until stops one before the end. downTo counts backwards. step changes how big each jump is. Mixing them wrong can make an empty range.

open as a page

When is the else branch mandatory for an if in Kotlin, and when is it optional? Show an example of each.

level: middleimportance: must knowfreq 60%

basics

~20 s

If you use the if's result (assign it, return it, pass it), you must include else so every path produces a value. If you ignore the result and just run side effects, else is optional.

open as a page

Explain the difference between `continue@outer` and `break@outer` in a nested loop, and trace the output of a short example for each.

level: middleimportance: must knowfreq 55%

basics

~20 s

break@outer quits the named outer loop completely. continue@outer skips the rest of the current inner work and jumps to the outer loop's next iteration. Both target the labeled loop, but one stops it, the other advances it.

open as a page

You have a `when` expression over a `sealed interface`. Should you add an `else` branch? What are the trade-offs of including vs omitting it?

level: middleimportance: must knowfreq 65%

basics

~20 s

Usually omit the else. Then if someone adds a new subtype later, the code stops compiling and points you to every when you must update. Adding an else hides those spots and lets bugs slip through.

open as a page

How do `is`/`!is` branches work in `when`, and how do smart casts apply inside those branches?

level: seniorimportance: must knowfreq 65%

basics

~20 s

is Type matches when the subject is that type; !is is the opposite. Inside an is branch Kotlin smart-casts the value to that type automatically, so you can call its members without an explicit cast.

open as a page

Compare while and do-while in Kotlin, and explain when you'd choose a while loop over a for/range loop. What's special about the scope of variables declared in a do-while condition?

level: middleimportance: should knowfreq 45%

basics

~10 s

while checks the condition before running the body, so it may run zero times. do-while runs the body once first, then checks. Use while when the number of steps isn't a simple count.

open as a page

You need both the index and the element while iterating a list. Show the idiomatic Kotlin way and explain what withIndex() returns and how it compares to indices.

level: middleimportance: should knowfreq 55%

basics

~10 s

Use withIndex() in the for loop. It gives you a pair of (position, value) for each item, so you can read both at once without manually tracking a counter.

open as a page

What determines the result type of an if/else expression in Kotlin? Walk through if (c) 1 else 2, if (c) 1 else null, and a branch that throws.

level: middleimportance: should knowfreq 45%

basics

~20 s

The type is the closest common type that fits both branches. Two Ints give Int. Int and null give Int? (nullable). A branch that throws contributes nothing, so the type comes from the other branch.

open as a page

Where exactly must a loop label be declared in Kotlin, and what are the syntax rules for declaring and referencing it? Show a valid and an invalid placement.

level: middleimportance: should knowfreq 40%

basics

~20 s

Write the label as a name followed by @ directly in front of the loop, like loop@ for (...). To use it, write break@loop or continue@loop with the @ before the name. The label must come before the loop, not after.

open as a page

What is an implicit label in Kotlin, and how do you give a lambda an explicit label to target with a qualified return?

level: middleimportance: should knowfreq 55%

basics

~20 s

By default Kotlin names a lambda's label after the function it's passed to, so you can write return@forEach. You can override that by writing your own label before the lambda, like loop@ { ... }, and then use return@loop.

open as a page

A developer writes `value?.let { return }` expecting to return early from a helper. What actually happens, and what is the subtle bug when this idiom is misused?

level: middleimportance: should knowfreq 40%

basics

~20 s

let, run, apply, etc. are inline, so a bare return inside their lambda returns from the whole enclosing function — not just from the let block. People expect it to stop only the let, which is the bug.

open as a page

You want a side-effect-only `when` over an enum to be checked for exhaustiveness so adding an enum entry breaks the build. How do you make a statement `when` exhaustive in Kotlin?

level: middleimportance: should knowfreq 45%

basics

~20 s

Make the when produce a value so it counts as an expression. The simplest way is to put it after a tiny expression like assigning its result, so the compiler insists every case is handled. In modern Kotlin you also just get a warning automatically.

open as a page

Explain comma-grouped branches and `in`/`!in` range checks in `when`. What does comma between conditions mean?

level: middleimportance: should knowfreq 55%

basics

~10 s

A comma between conditions means OR: the branch matches if any listed value matches. in range checks membership and !in checks non-membership, working with ranges, collections, or anything defining contains.

open as a page

What is the subjectless (argument-less) form of `when`, and when would you use it over an `if`/`else if` chain?

level: middleimportance: should knowfreq 60%

basics

~20 s

Write when {} with no value in the parentheses. Each branch is its own boolean condition, and the first true one wins. It reads more cleanly than a long if/else if chain when you have several unrelated conditions.

open as a page

How does Kotlin's for loop decide that a type is iterable, and how would you make a custom type (or even a range of a custom type) usable in a for loop? What desugaring happens under the hood?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A for loop works on anything that provides an iterator. You give your type an iterator() function (or add one as an extension), returning an object with hasNext() and next(). Then for just works on it.

open as a page

How does if-as-an-expression combine with single-expression functions and block branches? When would you prefer it over when?

level: seniorimportance: should knowfreq 35%

basics

~20 s

You can make a function body a single if expression with =, no return needed. Branches can be blocks whose last line is the value. Use if for two outcomes; reach for when when you have several cases.

open as a page

A candidate tries to use `break` inside a `forEach { }` lambda to stop iteration and it won't compile. Explain why, and what the correct labeled-jump mechanism is in that case versus a real loop.

level: seniorimportance: should knowfreq 45%

basics

~20 s

break and continue only work in real loops (for, while). forEach { } is a function taking a lambda, not a loop, so break won't compile there. Inside the lambda you can only return@forEach to skip the current item, which is like continue, not break.

open as a page

Labeled break/continue can express any nested-loop exit, but is it always the best choice? Discuss the trade-offs and the main alternative for escaping nested loops in Kotlin.

level: seniorimportance: should knowfreq 30%

basics

~20 s

Labels work, but deeply nested labeled jumps get hard to read. Often it's cleaner to pull the nested loops into their own function and use a normal return to exit everything at once, or use a collection operation like firstOrNull.

open as a page

Explain how `inline`, `noinline`, and `crossinline` interact with non-local returns from lambda parameters.

level: seniorimportance: should knowfreq 45%

basics

~20 s

Only lambdas of an inline function can use a bare (non-local) return. Marking a parameter noinline removes that ability. crossinline keeps the lambda inlined but forbids non-local returns, because the lambda might be invoked from another context.

open as a page

Explain how the compiler decides a `when` over a sealed type is exhaustive, including the role of subtype visibility/module boundaries and nullability of the subject.

level: seniorimportance: should knowfreq 35%

basics

~20 s

The compiler can only check a when if it knows every subtype. Sealed types declare all their subtypes up front, so it can. If the value can be null, you must also handle the null case, or the when is not complete.

open as a page

What are the common correctness pitfalls when refactoring a long `if`/`else if` chain into `when`, particularly around branch order, equality, and side effects?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Branch order matters because the first match wins, so overlapping conditions can change behavior if reordered. Also remember when uses structural equality (==), evaluates the subject once, and stops at the first match — so don't rely on later branches' side effects.

open as a page

A teammate argues every sealed `when` should have a defensive `else -> error("unreachable")` 'just in case'. Critique this from a maintainability and correctness standpoint, and describe when it is actually justified.

level: principalimportance: should knowfreq 25%

basics

~20 s

Adding else -> error(...) to a sealed when usually hurts. It silences the compiler check, so when someone adds a new case the code still compiles and only fails at runtime. Without else, the build breaks and points you to every place to fix.

open as a page

Are Kotlin range/for loops zero-overhead compared to handwritten index loops, and what allocations or boxing might occur? Discuss IntRange vs an IntProgression and the .indices iteration pattern.

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Plain integer for-loops like for (i in 0 until n) compile down to fast counter loops with no boxing. Costs can appear if you turn a range into an object, use very large Long ranges, or box values via generics.

open as a page

Why is val ms = if (cond) listOf(1) else mutableListOf<Int>() a subtle pitfall, and what general principle does it illustrate about if expressions and types?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

The two branches have different list types, so the variable gets the broader common type (List), losing the mutable API. Watch the inferred type: an if expression widens to whatever both branches share, which may be less than you expect.

open as a page

showing 1–30 of 31