skip to content

Why is the bound written as <T : Comparable<T>> in functions like a generic max(), and what does this self-referential bound enforce?

level: seniorimportance: should knowfreq 45%

answer

  1. Recursive / F-bounded: T inside its own bound
  2. Requires Comparable<T> => compareTo(T)
  3. >= desugars to compareTo >= 0
  4. Self-ref = comparable to its OWN type
  5. maxOf/minOf/sorted use it

basics

~10 s

<T : Comparable<T>> means T must be comparable to its own type. It lets you call a.compareTo(b) on two T values, which is what max/min need to order them.

solid answer

~40 s

`<T : Comparable<T>>` is a **recursive (F-bounded) generic** constraint: the type parameter `T` appears inside its own bound. It requires that `T` implements `Comparable<T>`, i.e. that any `T` can be compared to another `T` via `compareTo`. This is exactly what ordering functions need: `fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a >= b) a else b` — the `>=` desugars to `a.compareTo(b) >= 0`, available only because the bound supplies `Comparable<T>`. Without the bound, `T`'s only members come from `Any?`, and `compareTo` would not type-check. The self-reference ensures you compare a `T` against the *same* `T`, not some unrelated `Comparable<Other>`. Standard library `kotlin.comparisons.maxOf`, `minOf`, and `sorted()` on `Iterable<T : Comparable<T>>` use this pattern.

code

kotlin · 9 lines
kotlin
fun <T : Comparable<T>> clamp(value: T, low: T, high: T): T =
    when {
        value < low  -> low
        value > high -> high
        else         -> value
    }

clamp(5, 1, 10)              // Int : Comparable<Int>
clamp("m", "a", "z")        // String : Comparable<String>

go deeper

for a junior

Recognizes the bound is needed so values can be compared with </>.

for a middle

Explains that Comparable<T> supplies compareTo and that operators desugar to it.

for a senior

Names the F-bounded/recursive pattern and explains why self-reference (vs Comparable<*>) is required.

for a principal

Discusses Comparable's contravariance, comparison to a Comparator-based API, and design trade-offs for ordering generics in a library.

## The pattern ```kotlin fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a >= b) a else b ``` The bound `T : Comparable<T>` is **recursive** — `T` appears both as the parameter being bounded and as the type argument of `Comparable`. This is the **F-bounded polymorphism** idiom. ## What `Comparable<T>` provides `Comparable<in T>` declares a single member: ```kotlin operator fun compareTo(other: T): Int ``` It returns a negative number, zero, or a positive number when the receiver is less than, equal to, or greater than `other`. Kotlin's comparison operators `<`, `<=`, `>`, `>=` desugar to `compareTo` calls. So `a >= b` compiles only if `a` has a `compareTo(b)` — which the bound guarantees. ## Why self-referential? If you wrote merely `<T : Comparable<*>>`, you would know `T` is comparable to *something*, but not to another `T`. The function `maxOf(a: T, b: T)` must compare `a` to `b`, both of type `T`. Tying the type argument of `Comparable` back to `T` enforces 'comparable to its own type': ```kotlin class Version(val n: Int) : Comparable<Version> { override fun compareTo(other: Version) = n - other.n } maxOf(Version(1), Version(2)) // T = Version, satisfies Comparable<Version> ``` ## Without the bound it fails ```kotlin fun <T> brokenMax(a: T, b: T): T = if (a >= b) a else b // ERROR: >= unavailable on Any? ``` Because an unbounded `T` only has `Any?`'s members, `>=`/`compareTo` don't exist. ## Nullability `Comparable<T>` is itself non-null-friendly: declaring `<T : Comparable<T>>` does *not* by itself forbid `T?`, but `Comparable<T>` is the bound, so `T` must be a non-null type implementing it (a nullable type can't implement an interface). In practice these helpers operate on non-null `T`. ## Standard library `maxOf`, `minOf`, `coerceIn`, `sorted()` (`Iterable<T>.sorted(): List<T>` where `T : Comparable<T>`), and `TreeSet`-backed `sortedSetOf` all rely on this exact bound. ## Variance note `Comparable` is declared `Comparable<in T>` (contravariant). That contravariance lets a `Comparable<Number>` satisfy a need for `Comparable<Int>`, which broadens what types fit the recursive bound, but the core requirement — comparing a `T` to a `T` — remains.

  • What does the operator a >= b actually compile to under this bound?
    a.compareTo(b) >= 0. The bound Comparable<T> supplies compareTo, and Kotlin desugars relational operators into compareTo comparisons against 0.
  • Why not just use <T : Comparable<*>>?
    Comparable<*> only guarantees T compares to some unknown type, so you couldn't safely compare two T values to each other; the self-reference Comparable<T> ties it to the same type.

It's like requiring 'must be able to race against its own twin' — not just 'can race someone', but specifically race another of the exact same kind.

saying these in an interview costs you the question

  • Calling compareTo on an unbounded <T> and expecting it to compile
  • Thinking Comparable<*> is equivalent to Comparable<T>
  • Not knowing >= desugars to compareTo
  • Claiming the bound is needed for equals/hashCode rather than ordering
  • Confusing Comparable<T> (natural ordering) with a Comparator<T> parameter

context