Ranges & Progressions

Reviewed & published by Brayan K

A range is an interval of values with a start and an end, written with the .. operator — Kotlin uses ranges for looping, for testing membership with in, and inside when.

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.

Add step, downTo, or until and a range becomes a flexible, readable replacement for clumsy counter loops.

What You'll Learn in This Lesson

1️⃣ Building Ranges: .., until, downTo

The .. operator makes a closed range that includes both endpoints. The until keyword makes a half-open range that stops one short of the top — perfect for indices. The downTo keyword counts the other direction.

fun main() {
    // 1..5 is a CLOSED range: includes both ends -> 1,2,3,4,5
    for (i in 1..5) print(i)
    println()                         // -> 12345

    // 1 until 5 is HALF-OPEN: excludes the top -> 1,2,3,4
    for (i in 1 until 5) print(i)
    println()                         // -> 1234

    // downTo counts DOWN -> 5,4,3,2,1
    for (i in 5 downTo 1) print(i)
    println()                         // -> 54321
}

2️⃣ step and Character Ranges

A range with step becomes a progression that jumps by more than one each pass. The step value is always positive — direction comes from .. or downTo. Because characters are ordered, you can also build ranges of letters.

fun main() {
    // step changes the increment.
    for (i in 0..10 step 2) print("$i ")
    println()                          // -> 0 2 4 6 8 10

    // step works with downTo too:
    for (i in 9 downTo 1 step 3) print("$i ")
    println()                          // -> 9 6 3

    // A CharRange iterates letters in order:
    for (c in 'a'..'e') print(c)
    println()                          // -> abcde
}

3️⃣ in / !in and Ranges in when

The in operator asks "is this value inside the range?" and !in asks the opposite — both return a clean boolean. This makes range checks read almost like English, and they pair beautifully with when for bucketing a value into categories.

fun main() {
    val age = 25

    // 'in' checks membership in a range (true here):
    println(age in 18..65)            // -> true

    // '!in' checks NON-membership:
    println(age !in 18..65)           // -> false

    // Works for chars too:
    val grade = 'B'
    println(grade in 'A'..'F')        // -> true

    // Great inside an if:
    if (age in 13..19) println("teen") else println("not a teen")  // -> not a teen
}

Ranges inside when are the idiomatic way to grade or classify a number:

fun main() {
    val score = 73

    // Ranges shine inside 'when':
    val grade = when (score) {
        in 90..100 -> "A"
        in 80..89  -> "B"
        in 70..79  -> "C"
        in 60..69  -> "D"
        else       -> "F"
    }
    println("Grade: $grade")          // -> Grade: C

    // Count down and collect into a list:
    val countdown = (3 downTo 1).toList()
    println(countdown)                // -> [3, 2, 1]
}

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

// 🎯 YOUR TURN — replace each ___ then run main()
fun main() {
    // 1) Print even numbers 2,4,6,8,10 using a range + step
    for (i in 2..10 step ___) print("$i ")   // 👉 step 2
    println()

    // 2) Check whether 7 is in the range 1..5
    println(7 in 1..___)                       // 👉 1..5  -> false

    // ✅ Expected:
    // 2 4 6 8 10
    // false
}

Mini-Challenge: Rocket Countdown

Use downTo to count from 5 to 1, then announce liftoff.

// 🎯 MINI-CHALLENGE: a simple rocket countdown.
// Save as Countdown.kt and run main().
//
// 1. Use downTo to count from 5 down to 1, printing each on its own line.
// 2. After the loop, print "Liftoff!".
//
// ✅ Expected output:
//    5
//    4
//    3
//    2
//    1
//    Liftoff!

fun main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Ranges

GoalKotlin Syntax
Closed range1..10
Half-open range1 until 10
Count down10 downTo 1
Custom increment1..10 step 2
Membershipx in 1..10
Char range'a'..'z'
All valid indiceslist.indices

🎉 Lesson Complete!

Practice quiz

Which values does the closed range 1..5 include?

  • 1, 2, 3, 4
  • 1, 2, 3, 4, 5
  • 2, 3, 4, 5
  • 0, 1, 2, 3, 4

Answer: 1, 2, 3, 4, 5. The .. operator makes a closed range that includes both endpoints.

Which values does 1 until 5 produce?

  • 1, 2, 3, 4, 5
  • 2, 3, 4, 5
  • 1, 2, 3, 4
  • 1, 5

Answer: 1, 2, 3, 4. until is half-open: it excludes the upper bound, giving 1, 2, 3, 4.

What does 5 downTo 1 produce?

  • 1, 2, 3, 4, 5
  • An empty range
  • 5, 4, 3, 2, 1
  • 5, 1

Answer: 5, 4, 3, 2, 1. downTo counts downward from 5 to 1.

What does the step in 0..10 step 2 control?

  • The direction of iteration
  • The size of the increment between values
  • The starting value
  • Whether the end is included

Answer: The size of the increment between values. step changes the increment; 0..10 step 2 yields 0, 2, 4, 6, 8, 10.

What does the expression 7 in 1..5 evaluate to?

  • true
  • false
  • 7
  • It does not compile

Answer: false. in tests membership; 7 is not within 1..5, so the result is false.

What does writing 5..1 (with ..) produce?

  • 5, 4, 3, 2, 1
  • An empty range
  • 1, 2, 3, 4, 5
  • A compile error

Answer: An empty range. A .. range counting downward is empty; use 5 downTo 1 to count down.

Why must the step value always be positive, even with downTo?

  • Negative steps are slower
  • Direction comes from .. or downTo; step is only the magnitude
  • Kotlin rounds negatives to positives
  • It is a historical bug

Answer: Direction comes from .. or downTo; step is only the magnitude. Direction is decided by .. or downTo; a non-positive step throws IllegalArgumentException.

Which is the cleanest way to loop over all valid indices of a list?

  • 0..list.size
  • 1..list.size
  • for (i in list.indices)
  • list.size downTo 0

Answer: for (i in list.indices). list.indices is a built-in range of every valid index, avoiding off-by-one errors.

Can you iterate a Double range like 0.0..1.0 in a for loop?

  • Yes, with a default step of 0.1
  • Yes, always
  • No, Doubles support in checks but not for-loop iteration
  • Only with downTo

Answer: No, Doubles support in checks but not for-loop iteration. Double ranges support in membership tests but cannot be iterated in a for loop.

Where do ranges make grading logic especially readable?

  • Inside a when expression with in branches
  • Only inside while loops
  • In string templates
  • In class constructors

Answer: Inside a when expression with in branches. Ranges with in inside a when keep classification logic flat and readable.

Continue this course

Frequently asked questions

What is the difference between a range and a progression?

A range, such as 1..10, represents an interval between two endpoints and is mainly used for membership tests with in. A progression is an ordered, steppable sequence you iterate over — it has a start, an end, and a step. When you write 1..10 step 2 or 5 downTo 1, you are creating an IntProgression. Every range is iterable, so in everyday code the distinction rarely matters, but it explains why step and downTo produce something you loop over.

When should I use until instead of ..?

Use until when you want to exclude the upper bound, which is most common when looping over indices. for (i in 0 until list.size) visits every valid index 0, 1, ... size-1 without going one past the end. Use the closed .. range when you genuinely want both endpoints included, like 1..100 to sum the first hundred numbers.

Can I use ranges with characters and other types?

Yes. 'a'..'z' is a CharRange you can iterate or test with in, because characters have an order. Ranges work for any comparable type for the in membership check — for example a Double range like 0.0..1.0 supports x in 0.0..1.0 — though only integer-like ranges (Int, Long, Char) can be iterated in a for loop, since iteration needs a defined step.

Why does step have to be positive even with downTo?

The step value is always the magnitude of the increment, never the direction. Direction is decided by .. (up) or downTo (down). So 9 downTo 1 step 3 walks downward in jumps of 3. Passing a non-positive step throws an IllegalArgumentException, which keeps the loop's intent unambiguous.