What operator functions back `..`, `..<`, `downTo`, and `step`, and what does that imply for using them on your own or non-Int types?
answer
- `..`->rangeTo, `..<`->rangeUntil (operators)
- downTo/step are infix funcs, not operators
- Comparable types: `in` yes, iterate no
- Implement ClosedRange: start, endInclusive
- Iterable range needs discrete step
basics
~10 s.. calls rangeTo, ..< calls rangeUntil; downTo and step are infix library functions. Because .. and ..< are operators, you can define them on your own Comparable types to support in checks.
solid answer
~40 s`a..b` resolves to `a.rangeTo(b)` (`operator fun rangeTo`) and `a..<b` to `a.rangeUntil(b)` (`operator fun rangeUntil`, Kotlin 1.9+). `downTo` and `step` are plain `infix` stdlib functions, not operators, so you can't redefine their token. The standard library provides `rangeTo` for all numeric and `Comparable` types: for `Int`/`Long`/`Char` it yields iterable progressions; for arbitrary `Comparable<T>` (e.g. `Double`, `String`, `BigDecimal`, `LocalDate`) it yields a `ClosedRange<T>` that supports `in` membership but is **not** iterable. You can give your own type range support by declaring `operator fun rangeTo(other: MyType): ClosedRange<MyType>` (or returning a custom range). Implementing `ClosedRange` requires `start`, `endInclusive`, and `contains`; `OpenEndRange` backs `rangeUntil`. To make a custom range *iterable*, you'd return a type that is also an `Iterable`/progression, which is non-trivial for non-discrete types.
code
kotlin · 4 lines// `in` membership works for any Comparable
println("k" in "a".."z") // true (String ClosedRange)
println(2.5 in 0.0..5.0) // true (ClosedFloatingPointRange)
// but neither can be used in a for-loopgo deeper
Recognizes .. is an operator with a function name behind it, even if unsure which.
Maps ..->rangeTo and ..<->rangeUntil and knows downTo/step are infix functions.
Distinguishes ClosedRange (membership) from progression (iteration), and can add rangeTo to a Comparable type.
Designs domain range support (ClosedRange/OpenEndRange contracts), reasons about iterability for discrete vs continuous types, and the cost/benefit of custom progressions.
## The operator-to-function mapping Kotlin operators are sugar for conventionally-named functions: | Syntax | Resolves to | Kind | |--------|-------------|------| | `a..b` | `a.rangeTo(b)` | `operator fun rangeTo` | | `a..<b` | `a.rangeUntil(b)` | `operator fun rangeUntil` (1.9+) | | `a downTo b` | `a.downTo(b)` | `infix fun` (not an operator) | | `a step n` | `progression.step(n)` | `infix fun` (not an operator) | Only `..` and `..<` are **operators** — their token can be overloaded by defining the right function. `downTo` and `step` are ordinary infix functions; you call them by name and cannot bind new symbols to them. ## What the stdlib gives you - For `Int`, `Long`, `Char`: `rangeTo`/`rangeUntil` return **progressions** (`IntRange`, etc.) that are `ClosedRange`/`OpenEndRange` **and** `Iterable`. Hence `for` loops and `in` both work. - For any other `Comparable<T>` — `Double`, `String`, `BigDecimal`, `java.time.LocalDate`, etc. — `rangeTo` returns a `ClosedRange<T>` (a `ClosedFloatingPointRange` for `Double`). These support **`in`** membership but are **not iterable**, because there's no natural discrete step. ```kotlin import java.time.LocalDate val d = LocalDate.of(2026, 1, 1)..LocalDate.of(2026, 12, 31) println(LocalDate.of(2026, 6, 1) in d) // true — membership works // for (x in d) ... // does NOT compile — not iterable ``` ## Adding range support to your own type Declare `rangeTo` as an operator. The simplest contract is `ClosedRange<T>`: ```kotlin data class Version(val major: Int, val minor: Int) : Comparable<Version> { override fun compareTo(other: Version) = compareValuesBy(this, other, Version::major, Version::minor) } operator fun Version.rangeTo(end: Version): ClosedRange<Version> = object : ClosedRange<Version> { override val start = this@rangeTo override val endInclusive = end } val supported = Version(1, 0)..Version(2, 5) println(Version(1, 8) in supported) // true ``` `ClosedRange` supplies a default `contains` using `compareTo`, so you only provide `start` and `endInclusive`. For half-open support, implement `OpenEndRange` and an `operator fun rangeUntil`. ## Making it iterable To support `for`, the returned range must also be `Iterable` — typically a custom progression with a `step`/`next` notion. The stdlib does this only for discrete types (`Int`/`Long`/`Char`). For something like `Version` you'd hand-roll an iterator; for continuous types (`Double`) it's deliberately impossible. ## Practical takeaways - Use `..`/`..<` for membership on *any* `Comparable` — great for validation (`age in 18..65`, `now in start..end`). - Only reach for `for` loops on integral/char ranges. - Overload `rangeTo`/`rangeUntil` to make domain types read naturally with `in`.
- Can you overload `downTo` with the operator keyword?No — `downTo` is an infix function, not an operator. You can write your own infix `downTo`, but it isn't tied to a symbol like `..`.
- Why can you write `2.5 in 0.0..5.0` but not loop over it?`rangeTo` on Double returns a ClosedFloatingPointRange supporting `contains`/`in`, but it isn't an Iterable/progression — there's no discrete step.
- What interface must a type implement to back `..<`?`OpenEndRange<T>` together with an `operator fun rangeUntil` returning it.
saying these in an interview costs you the question
- Claiming `downTo`/`step` are overloadable operators
- Thinking every `..` range is iterable
- Confusing ClosedRange (membership) with progression (iteration)
- Not knowing `..<` maps to `rangeUntil`