Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
92: Range Expressions and step
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:
- Uses a
forloop with astepto print "Reminder scheduled for day X" for every 14th day starting at 1. - Ensures the loop does not exceed day 30.
- After the loop finishes, prints "Final Check scheduled for day 30".
// Your code here
There are no comments for now.