skip to content

In Kotlin, what do the comparison operators <, >, <=, >= actually translate to under the hood?

level: middleimportance: must knowfreq 65%

answer

  1. All four ordering ops -> compareTo, compared to 0
  2. compareTo: negative / zero / positive Int
  3. operator fun in Comparable<T>
  4. Implement one, get four + sorting
  5. == uses equals, NOT compareTo

basics

~10 s

They all call a single method named compareTo. The compiler rewrites a < b into a.compareTo(b) < 0, and similarly for the others, comparing the returned number against zero.

solid answer

~40 s

The four ordering operators desugar to calls on the `Comparable` interface's `compareTo(other): Int` function. The compiler rewrites: a < b -> a.compareTo(b) < 0; a > b -> a.compareTo(b) > 0; a <= b -> a.compareTo(b) <= 0; a >= b -> a.compareTo(b) >= 0. So any type that implements Comparable<T> (or declares operator fun compareTo) becomes usable with all four operators at once — you implement one function, not four. compareTo must return a negative Int if the receiver is less, zero if equal, positive if greater. Note that == and != do NOT desugar to compareTo; they use equals()/structural equality, which is why a Comparable type can have compareTo == 0 yet equals == false (e.g., BigDecimal("1.0") vs BigDecimal("1.00")).

code

kotlin · 10 lines
kotlin
data class Money(val cents: Long) : Comparable<Money> {
    override fun compareTo(other: Money) = cents.compareTo(other.cents)
}

fun main() {
    val a = Money(150)
    val b = Money(299)
    println(a < b)            // a.compareTo(b) < 0  -> true
    println(a >= Money(150))  // a.compareTo(..) >= 0 -> true
}

go deeper

for a junior

Knows the operators map to compareTo compared against zero.

for a middle

States the exact desugaring for all four and that Comparable supplies compareTo.

for a senior

Explains the compareTo/equals split and the BigDecimal scale inconsistency, plus compareValuesBy.

for a principal

Reasons about consistency contracts (compareTo must agree with equals for sorted collections) and the design value of a single source of truth for ordering.

## The desugaring rule Kotlin has no separate magic for each ordering operator. All four are defined in terms of **one** function, `compareTo`: | Operator | Desugars to | |----------|-------------| | `a < b` | `a.compareTo(b) < 0` | | `a > b` | `a.compareTo(b) > 0` | | `a <= b` | `a.compareTo(b) <= 0` | | `a >= b` | `a.compareTo(b) >= 0` | `compareTo` returns an `Int`: **negative** if the receiver is ordered before `b`, **zero** if they compare equal, **positive** if after. ## Comparable and the operator keyword The contract lives in the standard `Comparable<T>` interface: ```kotlin public interface Comparable<in T> { public operator fun compareTo(other: T): Int } ``` Note the `operator` modifier — that is what makes the `<`, `>`, `<=`, `>=` syntax legal. Implement it once and you get all four operators **plus** `.sorted()`, `maxOf`, `coerceIn`, ranges, etc. ```kotlin data class Version(val major: Int, val minor: Int) : Comparable<Version> { override fun compareTo(other: Version): Int = compareValuesBy(this, other, Version::major, Version::minor) } Version(2, 0) < Version(2, 1) // true -> compareTo returns negative ``` `compareValuesBy` is a stdlib helper that compares by a list of selectors in order — a clean way to avoid hand-written chains. ## Why == is different Equality operators `==`/`!=` do **not** use `compareTo`; they call `equals()` (structural equality). So a type can have `a.compareTo(b) == 0` while `a == b` is `false`. The canonical example is `BigDecimal`: `BigDecimal("1.0").compareTo(BigDecimal("1.00")) == 0` but `BigDecimal("1.0") != BigDecimal("1.00")` because their scales differ. This inconsistency means you should not put `BigDecimal` keys in a `TreeSet` and expect `HashSet` semantics. ## Practical implication Writing `operator fun compareTo` on a class — or implementing `Comparable` — unlocks ordering everywhere uniformly. You never override `<` and `>` separately; the language guarantees they stay consistent because they share one source of truth.

  • If I implement Comparable, do I still need to override equals for ==?
    Yes. == uses equals() (or data class auto-equals), entirely separate from compareTo. They can and sometimes do disagree.
  • What's a clean way to compare by multiple fields?
    Use the stdlib compareValuesBy(this, other, Type::field1, Type::field2, ...), which compares selectors in order and returns the first non-zero result.

saying these in an interview costs you the question

  • Claiming each operator has its own override
  • Saying < and == both route through compareTo
  • Returning a bool instead of an Int from compareTo
  • Thinking compareTo == 0 implies equals == true
  • Not knowing the operator keyword is required

context