Blog / January 15, 2022 / 5 mins read / By Suneet Agrawal

Kotlin Range Operator

Range Operator in Kotlin is a basic operator that is used to operate over a range. A range can be defined with a start value and an end value with and without inclusion.

The range operators can be used with for loops, if conditions or even in when operator. First, let to see a basic example of a range operator.

A basic range operator can be defined with .. having a lower range value to the left side and upper range value to the right side of ..

val x = 4

for (item in 1..10){
    println(item)
}

if ((1..10).contains(x)){
    println("X is in range")
}

when (x){
    in 1..10 -> {
        println("X is in range")
    }
}

Lets try to understand the .. operator and its return type first.

rangeTo() function

.. is an operator overloading to rangeTo() function which is defined in kotlin.ranges package.

rangeTo() function is an extension function to the Template class where the Template class should implement the Comparable interface.

That means we can create a range to any primitive or non primitive data type which implements the Comparable interface.

/**
 * Creates a range from this [Comparable] value to the specified [that] value.
 *
 * This value needs to be smaller than or equal to [that] value, otherwise the returned range will be empty.
 * @sample samples.ranges.Ranges.rangeFromComparable
 */
public operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)

ClosedRange

The rangeTo() function returns a ClosedRange interface object which has a start and an endInclusive variables and contains operator function which is used by the in operator and an isEmpty function.

package kotlin.ranges

/**
 * Represents a range of values (for example, numbers or characters).
 */
public interface ClosedRange<T: Comparable<T>> {
    /**
     * The minimum value in the range.
     */
    public val start: T

    /**
     * The maximum value in the range (inclusive).
     */
    public val endInclusive: T

    /**
     * Checks whether the specified [value] belongs to the range.
     */
    public operator fun contains(value: T): Boolean = value >= start && value <= endInclusive

    /**
     * Checks whether the range is empty.
     *
     * The range is empty if its start value is greater than the end value.
     */
    public fun isEmpty(): Boolean = start &gt; endInclusive
}

CharRange, IntRange & LongRange

ClosedRange is further extended by CharRange, IntRange and LongRange which provides the functionality of Iterable also along with closed range which is used by [for loops](/blogs/kotlin-for-loop/" target="_blank").

package kotlin.ranges

/**
 * A range of values of type `Char`.
 */
public class CharRange(start: Char, endInclusive: Char) : CharProgression(start, endInclusive, 1), ClosedRange&lt;Char&gt; {
    override val start: Char get() = first
    override val endInclusive: Char get() = last

    override fun contains(value: Char): Boolean = first <= value && value <= last

    /** 
     * Checks whether the range is empty.
     *
     * The range is empty if its start value is greater than the end value.
     */
    override fun isEmpty(): Boolean = first > last

    override fun equals(other: Any?): Boolean =
        other is CharRange && (isEmpty() && other.isEmpty() ||
        first == other.first && last == other.last)

    override fun hashCode(): Int =
        if (isEmpty()) -1 else (31 * first.code + last.code)

    override fun toString(): String = "$first..$last"

    companion object {
        /** An empty range of values of type Char. */
        public val EMPTY: CharRange = CharRange(1.toChar(), 0.toChar())
    }
}

/**
 * A range of values of type `Int`.
 */
public class IntRange(start: Int, endInclusive: Int) : IntProgression(start, endInclusive, 1), ClosedRange&lt;Int&gt; {
    override val start: Int get() = first
    override val endInclusive: Int get() = last

    override fun contains(value: Int): Boolean = first <= value && value <= last

    /** 
     * Checks whether the range is empty.
     *
     * The range is empty if its start value is greater than the end value.
     */
    override fun isEmpty(): Boolean = first > last

    override fun equals(other: Any?): Boolean =
        other is IntRange && (isEmpty() && other.isEmpty() ||
        first == other.first && last == other.last)

    override fun hashCode(): Int =
        if (isEmpty()) -1 else (31 * first + last)

    override fun toString(): String = "$first..$last"

    companion object {
        /** An empty range of values of type Int. */
        public val EMPTY: IntRange = IntRange(1, 0)
    }
}

/**
 * A range of values of type `Long`.
 */

public class LongRange(start: Long, endInclusive: Long) : LongProgression(start, endInclusive, 1), ClosedRange&lt;Long&gt; {
    override val start: Long get() = first
    override val endInclusive: Long get() = last

    override fun contains(value: Long): Boolean = first <= value && value <= last

    /** 
     * Checks whether the range is empty.
     *
     * The range is empty if its start value is greater than the end value.
     */
    override fun isEmpty(): Boolean = first > last

    override fun equals(other: Any?): Boolean =
        other is LongRange && (isEmpty() && other.isEmpty() ||
        first == other.first && last == other.last)

    override fun hashCode(): Int =
        if (isEmpty()) -1 else (31 * (first xor (first ushr 32)) + (last xor (last ushr 32))).toInt()

    override fun toString(): String = "$first..$last"

    companion object {
        /** An empty range of values of type Long. */
        public val EMPTY: LongRange = LongRange(1, 0)
    }
}

downTo

To iterate over a range in reverse order, we can use downTo operator which is an extension function on different types like Int, Long, Char and provides the reverse iteration with step size as -1.

val x = 4

for (item in 10 downTo 1){
    println(item)
}

if ((10 downTo 1).contains(x)){
    println("X is in range")
}

when (x){
    in 10 downTo 1 -> {
        println("X is in range")
    }
}

until

The until operator can be used to exclude the endElement. The iterator or the if and when condition will not include the end element in case of until.

val x = 4

for (item in 1 until 10){
    println(item)
}

if ((1 until 10).contains(x)){
    println("X is in range")
}

when (x){
    in 1 until 10 -> {
        println("X is in range")
    }
}

step

The step can be used to change the step size of the iteration over the range.

val x = 4

for (item in 1..10 step 2){
    println(item)
}

if ((1 until 10 step 2).contains(x)){
    println("X is in range")
}

when (x){
    in 10 downTo 1 step 2 -> {
        println("X is in range")
    }
}
Comments