Kotlin ranges represent an ordered sequence of values with defined start and end points. A range represents an ordered set of values with a defined start and end. By default, it increments by 1 at each step. Ranges implement the ClosedRange<T>
interface and are inclusive by default, meaning both endpoints are included in the range.
The primary advantage of Kotlin ranges is their simplicity and readability. Instead of writing traditional loop constructs, you can express iterations and value checks more naturally using range syntax.
// Traditional approach (verbose)
for (i = 0; i < 10; i++) {
println(i)
}
// Kotlin range approach (concise)
for (i in 0..9) {
println(i)
}
The most common way to create a range is using the ..
operator, which creates a closed-ended range including both start and end values:
val numberRange = 1..5 // Creates range: 1, 2, 3, 4, 5
val charRange = 'a'..'e' // Creates range: a, b, c, d, e
Kotlin provides several functions for creating ranges:
The rangeTo()
function is equivalent to the ..
operator:
val range1 = 1.rangeTo(5) // Same as 1..5
To create an open-ended range, call the .rangeUntil() function with the ..< operator. This includes the start value but excludes the end value:
val openRange = 1..<5 // Creates range: 1, 2, 3, 4 (excludes 5)
For creating descending ranges, use the downTo()
function:
val descendingRange = 5 downTo 1 // Creates range: 5, 4, 3, 2, 1
Every Kotlin range has three fundamental properties:
val range = 2..8
println(range.first) // Output: 2
println(range.last) // Output: 8
println(range.step) // Output: 1
Check if a value exists within a range using the contains()
method or the in
operator:
val range = 10..20
println(15 in range) // Output: true
println(range.contains(25)) // Output: false
Determine if a range is empty:
val emptyRange = 5..3 // Invalid range
println(emptyRange.isEmpty()) // Output: true
By default, ranges increment by 1. You can modify this using the step()
function:
val evenNumbers = 2..10 step 2 // Creates: 2, 4, 6, 8, 10
val multiplesOfFive = 5..25 step 5 // Creates: 5, 10, 15, 20, 25
Combine downTo
with step
for custom descending progressions:
val countdown = 10 downTo 0 step 2 // Creates: 10, 8, 6, 4, 2, 0
val steppedRange = 1..10 step 3
println(steppedRange.first) // Output: 1
println(steppedRange.last) // Output: 10
println(steppedRange.step) // Output: 3
Integer ranges are the most commonly used type:
// Basic integer range
val basicRange = 1..10
// Range with step
val oddNumbers = 1..20 step 2
// Descending range
val reverseRange = 100 downTo 90
Create ranges of characters for alphabet operations:
val lowercase = 'a'..'z'
val uppercase = 'A'..'Z'
val subset = 'm'..'r' // Creates: m, n, o, p, q, r
For working with larger numbers:
val longRange = 1000L..2000L
val largeStepped = 0L..1000000L step 100000L
Ranges are particularly useful for iterating over for loops:
// Forward iteration
for (i in 1..5) {
println("Number: $i")
}
// Reverse iteration
for (i in 5 downTo 1) {
println("Countdown: $i")
}
// Step iteration
for (i in 0..100 step 10) {
println("Decade: $i")
}
Use the forEach
function for functional-style iteration:
(1..5).forEach { number ->
println("Processing: $number")
}
// Shortened syntax
(1..5).forEach(::println)
For more control, use iterators:
val range = 1..5
val iterator = range.iterator()
while (iterator.hasNext()) {
val value = iterator.next()
println("Iterator value: $value")
}
Reverse any range using the reversed()
function:
val original = 1..5
val reversed = original.reversed() // Creates: 5, 4, 3, 2, 1
reversed.forEach { println(it) }
Apply functional programming operations to ranges:
val range = 1..10
// Filter even numbers
val evenNumbers = range.filter { it % 2 == 0 }
println(evenNumbers) // Output: [2, 4, 6, 8, 10]
// Map to squares
val squares = range.map { it * it }
println(squares) // Output: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// Reduce to sum
val sum = range.reduce { acc, value -> acc + value }
println(sum) // Output: 55
// Calculate statistics
println("Min: ${range.minOrNull()}") // Output: Min: 1
println("Max: ${range.maxOrNull()}") // Output: Max: 10
println("Average: ${range.average()}") // Output: Average: 5.5
println("Count: ${range.count()}") // Output: Count: 10
Create exclusive ranges using until
:
// Exclusive end range
for (i in 1 until 5) {
println(i) // Prints: 1, 2, 3, 4 (excludes 5)
}
// Array indexing example
val array = arrayOf("A", "B", "C", "D", "E")
for (index in 0 until array.size) {
println("${index}: ${array[index]}")
}
Check value membership efficiently:
val validScores = 0..100
val userScore = 85
when {
userScore in validScores -> println("Valid score: $userScore")
else -> println("Invalid score: $userScore")
}
By integrating when with ranges, we have crisp and intelligible range checks, which enhances the readability of our Kotlin program:
fun evaluateGrade(score: Int): String {
return when (score) {
in 90..100 -> "Excellent"
in 80..89 -> "Good"
in 70..79 -> "Average"
in 60..69 -> "Below Average"
in 0..59 -> "Fail"
else -> "Invalid Score"
}
}
println(evaluateGrade(87)) // Output: Good
val temperature = 25
val weather = when (temperature) {
in -10..0 -> "Freezing"
in 1..15 -> "Cold"
in 16..25 -> "Mild"
in 26..35 -> "Warm"
in 36..50 -> "Hot"
else -> "Extreme"
}
println("Weather condition: $weather") // Output: Weather condition: Mild
It’s also possible to create a range over custom objects. For that, the only requirement is to extend the Comparable interface:
enum class Priority(val level: Int) : Comparable<Priority> {
LOW(1), MEDIUM(2), HIGH(3), CRITICAL(4);
override fun compareTo(other: Priority): Int = this.level.compareTo(other.level)
}
fun checkPriorityRange() {
val priorityRange = Priority.LOW..Priority.HIGH
println(Priority.MEDIUM in priorityRange) // Output: true
println(Priority.CRITICAL in priorityRange) // Output: false
}
import java.time.LocalDate
fun createDateRange(): ClosedRange<LocalDate> {
val startDate = LocalDate.of(2025, 1, 1)
val endDate = LocalDate.of(2025, 12, 31)
return startDate..endDate
}
fun isDateInCurrentYear(date: LocalDate): Boolean {
val yearRange = createDateRange()
return date in yearRange
}
Ranges are memory-efficient because they don’t store all values in memory. Instead, they calculate values on-demand during iteration:
// This doesn't create a million-element array in memory
val largeRange = 1..1_000_000
// Values are generated as needed
for (i in largeRange step 100_000) {
println(i) // Prints: 1, 100001, 200001, etc.
}
Kotlin’s for loops and ranges are optimized, but follow these tips for performance: Use until or step to minimize iterations:
// Efficient: Uses until to avoid unnecessary iteration
for (i in 0 until array.size) {
processElement(array[i])
}
// Efficient: Uses step to skip unnecessary values
for (i in 0..1000 step 50) {
performExpensiveOperation(i)
}
fun demonstrateArrayRanges() {
val numbers = arrayOf(10, 20, 30, 40, 50)
// Iterate through array indices
for (index in numbers.indices) {
println("Index $index: ${numbers[index]}")
}
// Process specific range of elements
for (index in 1..3) {
println("Element at $index: ${numbers[index]}")
}
// Reverse iteration through array
for (index in numbers.indices.reversed()) {
println("Reverse index $index: ${numbers[index]}")
}
}
fun processStringWithRanges(text: String) {
// Process each character position
for (position in text.indices) {
val char = text[position]
println("Position $position: '$char'")
}
// Extract substring using range
val middleRange = 2..5
if (text.length > 5) {
val substring = text.substring(middleRange)
println("Middle portion: $substring")
}
}
fun validateInput(value: Int): Boolean {
val validRange = 1..100
return value in validRange
}
fun categorizeAge(age: Int): String {
return when (age) {
in 0..12 -> "Child"
in 13..19 -> "Teenager"
in 20..64 -> "Adult"
in 65..120 -> "Senior"
else -> "Invalid Age"
}
}
Here’s a comprehensive example demonstrating various Kotlin range features:
import kotlin.random.Random
fun main() {
println("=== Kotlin Ranges Demonstration ===\n")
// 1. Basic range creation and iteration
println("1. Basic Integer Range:")
val basicRange = 1..5
basicRange.forEach { print("$it ") }
println("\n")
// 2. Character ranges
println("2. Character Range:")
val charRange = 'A'..'E'
for (char in charRange) {
print("$char ")
}
println("\n")
// 3. Step functionality
println("3. Range with Step:")
val steppedRange = 2..20 step 3
steppedRange.forEach { print("$it ") }
println("\n")
// 4. Descending ranges
println("4. Descending Range:")
val descendingRange = 10 downTo 5
descendingRange.forEach { print("$it ") }
println("\n")
// 5. Open-ended ranges
println("5. Open-ended Range (until):")
for (i in 1 until 5) {
print("$i ")
}
println("\n")
// 6. Range properties
println("6. Range Properties:")
val propertiesRange = 10..50 step 5
println("First: ${propertiesRange.first}")
println("Last: ${propertiesRange.last}")
println("Step: ${propertiesRange.step}")
println()
// 7. Membership testing
println("7. Membership Testing:")
val testRange = 1..100
val randomNumber = Random.nextInt(1, 150)
println("Random number: $randomNumber")
println("Is in range 1..100: ${randomNumber in testRange}")
println()
// 8. Collection operations
println("8. Collection Operations on Range:")
val operationsRange = 1..10
val evenNumbers = operationsRange.filter { it % 2 == 0 }
val squares = operationsRange.map { it * it }
val sum = operationsRange.sum()
println("Original range: ${operationsRange.toList()}")
println("Even numbers: $evenNumbers")
println("Squares: $squares")
println("Sum: $sum")
println("Average: ${operationsRange.average()}")
println()
// 9. Conditional range checking
println("9. Grade Evaluation:")
val scores = listOf(95, 87, 76, 64, 43)
scores.forEach { score ->
val grade = when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
in 60..69 -> "D"
else -> "F"
}
println("Score $score: Grade $grade")
}
println()
// 10. Advanced range manipulation
println("10. Advanced Range Operations:")
val baseRange = 1..20
val reversedRange = baseRange.reversed()
val filteredRange = baseRange.filter { it % 3 == 0 }
println("Original: ${baseRange.take(5).toList()}...")
println("Reversed: ${reversedRange.take(5).toList()}...")
println("Multiples of 3: $filteredRange")
// 11. Array indexing with ranges
println("\n11. Array Operations:")
val fruits = arrayOf("Apple", "Banana", "Cherry", "Date", "Elderberry")
println("All fruits:")
for (index in fruits.indices) {
println(" $index: ${fruits[index]}")
}
println("First three fruits:")
for (index in 0..2) {
if (index < fruits.size) {
println(" ${fruits[index]}")
}
}
// 12. Custom validation function
println("\n12. Age Category Validation:")
val ages = listOf(8, 16, 25, 45, 70, 150)
ages.forEach { age ->
val category = when (age) {
in 0..12 -> "Child"
in 13..19 -> "Teenager"
in 20..64 -> "Adult"
in 65..120 -> "Senior"
else -> "Invalid"
}
println("Age $age: $category")
}
}
When you run the complete example above, you’ll see:
=== Kotlin Ranges Demonstration ===
1. Basic Integer Range:
1 2 3 4 5
2. Character Range:
A B C D E
3. Range with Step:
2 5 8 11 14 17 20
4. Descending Range:
10 9 8 7 6 5
5. Open-ended Range (until):
1 2 3 4
6. Range Properties:
First: 10
Last: 50
Step: 5
7. Membership Testing:
Random number: 73
Is in range 1..100: true
8. Collection Operations on Range:
Original range: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Even numbers: [2, 4, 6, 8, 10]
Squares: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Sum: 55
Average: 5.5
9. Grade Evaluation:
Score 95: Grade A
Score 87: Grade B
Score 76: Grade C
Score 64: Grade D
Score 43: Grade F
10. Advanced Range Operations:
Original: [1, 2, 3, 4, 5]...
Reversed: [20, 19, 18, 17, 16]...
Multiples of 3: [3, 6, 9, 12, 15, 18]
11. Array Operations:
All fruits:
0: Apple
1: Banana
2: Cherry
3: Date
4: Elderberry
First three fruits:
Apple
Banana
Cherry
12. Age Category Validation:
Age 8: Child
Age 16: Teenager
Age 25: Adult
Age 45: Adult
Age 70: Senior
Age 150: Invalid
Kotlin ranges provide an elegant and powerful way to work with sequences of values. From simple iterations to complex conditional logic, mastering ranges will significantly improve your Kotlin programming skills.