skip to content

What is the difference between `..` and `..<` / `until`, and when would you reach for the half-open form?

level: middleimportance: must knowfreq 70%

answer

  1. `..<` / `until` = exclude the end
  2. `..` includes end, `..<` does not
  3. Index loops: `0..<list.size`
  4. `3..<3` empty; `3..3` = [3]
  5. `..<` -> rangeUntil; `until` -> stdlib infix fun

basics

~10 s

.. includes the last value; ..< (and the older until) stops just before it. Use the half-open form for index loops like 0..<list.size so you don't run off the end.

solid answer

~40 s

`a..b` is a closed range — both ends included. `a..<b` (the modern operator, Kotlin 1.9+) and `a until b` (the infix function) are **half-open**: they include `a` but **exclude** `b`. So `0..<5` yields 0,1,2,3,4. The half-open form is the idiomatic way to iterate indices: `for (i in 0..<list.size)` visits every valid index without the off-by-one risk of `0..list.size`. `..<` desugars to `rangeUntil`, while `until` is a stdlib infix extension. They're equivalent in result; `..<` is preferred today for symmetry with `..`. A subtle edge: `0 until Int.MIN_VALUE` and similar produce empty ranges, and `a until Int.MIN_VALUE` can't be expressed by decrementing below the type's minimum.

code

kotlin · 5 lines
kotlin
val xs = listOf(10, 20, 30)
for (i in 0..<xs.size) print("${xs[i]} ") // 10 20 30
println()
println((0..<0).isEmpty())  // true
println((0..0).isEmpty())    // false

go deeper

for a junior

Knows ..</until excludes the end and uses it for index loops.

for a middle

Explains both produce identical half-open progressions and the empty-vs-single-element edge at equal bounds.

for a senior

Knows ..< desugars to rangeUntil (1.9+) vs until being an older infix extension, and why ..< is now idiomatic.

for a principal

Reasons about type-minimum edge cases, list.indices equivalence, and consistency of half-open conventions across the stdlib.

## Closed vs half-open Kotlin gives you two flavours of ascending range: - **Closed / inclusive:** `a..b` — contains `a` through `b`, **including `b`**. - **Half-open:** `a..<b` or `a until b` — contains `a` up to but **excluding `b`**. ```kotlin println((1..5).toList()) // [1, 2, 3, 4, 5] println((1..<5).toList()) // [1, 2, 3, 4] println((1 until 5).toList()) // [1, 2, 3, 4] -- same as above ``` ## `..<` vs `until` - **`..<`** is the **range-until operator**, introduced in Kotlin 1.9 (stabilised from an earlier preview). It desugars to `a.rangeUntil(b)`. - **`until`** is an older **infix extension function** in the standard library (`infix fun Int.until(to: Int)`). - They produce the **same** progression. `..<` is now preferred because it reads symmetrically with `..` and avoids the word `until` (which some misread as inclusive). ## Why half-open is the index idiom Collections are zero-indexed with valid indices `0` to `size - 1`. The half-open form expresses exactly that without arithmetic: ```kotlin val xs = listOf("a", "b", "c") for (i in 0..<xs.size) println(xs[i]) // i = 0,1,2 (size is 3) // for (i in 0..xs.size) ... // BUG: i reaches 3 -> IndexOutOfBounds ``` This mirrors how half-open intervals work in math (`[a, b)`) and in most languages' loop conventions, eliminating the classic off-by-one error. ## Edge cases - If `a == b`, the half-open range is **empty** (`3..<3` has zero elements) whereas `3..3` has exactly one element (`[3]`). - If `a > b`, both forms are empty. - `a..<b` cannot represent ranges ending below the type minimum; for `Int`, `x until Int.MIN_VALUE` is always empty. ## Quick reference | Form | Includes start | Includes end | Notes | |------|----------------|--------------|-------| | `a..b` | yes | yes | closed | | `a..<b` | yes | no | half-open, 1.9+ operator | | `a until b` | yes | no | infix function, older |

  • Why prefer `..<` over `until` in new code?
    It's symmetric with `..`, avoids the ambiguous word 'until', and is a first-class operator (`rangeUntil`).
  • How many elements does `3..<3` have?
    Zero — half-open with equal bounds is empty, unlike `3..3` which has one element.
  • What's the right range to iterate every index of a list?
    `0..<list.size` (or `list.indices`, which is equivalent).

A movie that runs from 8:00 up until 10:00 — it ends right before 10, not at 10.

saying these in an interview costs you the question

  • Thinking `until` is inclusive of the upper bound
  • Using `0..list.size` for index loops (off-by-one)
  • Believing `..<` and `until` give different results
  • Saying `3..<3` contains 3

context