Skip to Content
Course content

92: Range Expressions and step

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've likely used a basic for loop to iterate through a list, but there are times when you don't want to visit every single element. Maybe you're sampling every tenth record from a dataset, or you're building a countdown timer. When I first started with Kotlin, I tended to fall back on the habits I had from C++ or Java, which usually meant managing a manual counter variable. It works, but it's a recipe for bugs.

The Manual Counter Headache

Imagine you're writing a script to sample data from a set of 100 pages, but you only want to check every 5th page to save on API credits. The "old school" way to do this is using a while loop and manually incrementing your index. It looks something like this:

var page = 1
while (page <= 100) {
    println("Sampling page $page")
    page += 5
}

Now, this seems harmless, but it's fragile. You're managing the state of page outside the loop logic. If you add a continue statement somewhere inside that loop for error handling, and you forget to increment page before that continue, you've just created an infinite loop. I've spent way too many hours debugging production hangs because of a missing i++ in a complex loop.

Letting the Range Handle the Heavy Lifting

Kotlin gives us Range Expressions to make this declarative. Instead of telling the computer how to increment the counter, you tell it what the sequence looks like. By using the step modifier, you move the increment logic into the range definition itself.

for (page in 1..100 step 5) {
    println("Sampling page $page")
}

This is significantly cleaner. The step keyword tells Kotlin to jump by 5 every time. There is no external variable to accidentally forget to increment, and the intent of the code is immediately obvious to anyone reading it. It's a "set it and forget it" approach to iteration.

The Danger of the Inclusive Edge

One thing that trips people up is the difference between .. and until. The .. operator creates a closed range, meaning the end value is included. In our pagination example, 1..100 includes 100. But if you're iterating over the indices of an array or a list, that's a disaster because lists are zero-indexed.

If you have a list of 10 items, the indices are 0 through 9. Using 0..list.size will throw an IndexOutOfBoundsException the moment it hits 10. This is where until comes in. It creates a range that is exclusive of the end value. I always use until when dealing with collection sizes to avoid that dreaded crash.

val items = listOf("A", "B", "C", "D", "E")
for (i in 0 until items.size step 2) {
    println("Item at index $i: ${items[i]}")
}

Reversing the Flow

Finally, there's the issue of counting backward. If you try to write for (i in 10..1), Kotlin won't throw an error, but the loop simply won't execute. The .. operator only works for ascending ranges. To go backward, you have to use downTo.

When you combine downTo with step, you get a very powerful way to handle reverse sequences without having to do weird math inside the loop body. Whether you're clearing a stack or printing a countdown, downTo is the tool for the job.

for (i in 10 downTo 1 step 3) {
    println("T-minus $i seconds...")
}



📋 Practical Task

Build a Bi-Weekly Reminder Scheduler

You are building a system that schedules reminders for a 30-day month. The user wants a reminder every 14 days (bi-weekly), starting from day 1. However, the system must also print a "Final Check" message on the very last day of the month (day 30), regardless of where the bi-weekly cycle lands.

Write a program that:

  1. Uses a for loop with a step to print "Reminder scheduled for day X" for every 14th day starting at 1.
  2. Ensures the loop does not exceed day 30.
  3. After the loop finishes, prints "Final Check scheduled for day 30".
// Your code here
Rating
0 0

There are no comments for now.

to be the first to leave a comment.