A teammate writes `fun <T> mid(a: T, b: T) where T : Number, T : Comparable<T>` and calls `mid(1, 2)`. It compiles, but `mid(1.0, 2)` does not. Explain why, and what the multi-bound `where` actually requires of the argument types.
answer
- `T : Comparable<T>` = comparable to its OWN type (F-bound)
- Multi-bound `where` needs ONE T meeting ALL constraints
- `Number` is NOT `Comparable<Number>`
- Mixed numeric args -> no self-comparable common supertype
- Fix: same-typed args or drop self-comparability
basics
~20 sT must be one single type that is both a Number and Comparable to itself. mid(1, 2) infers T = Int, which qualifies. mid(1.0, 2) mixes Double and Int, so no single T satisfies both arguments and the bounds, and inference fails.
solid answer
~40 sThe `where` clause forces the compiler to find **one** type `T` that simultaneously satisfies `T : Number` and `T : Comparable<T>` **and** is a common type for all arguments. `Int` is `Comparable<Int>` and a `Number`, so `mid(1, 2)` infers `T = Int`. For `mid(1.0, 2)`, the natural common supertype of `Double` and `Int` is `Number` (or `Comparable<*>`), but `Number` is **not** `Comparable<Number>`, so the `Comparable<T>` bound cannot be satisfied — there is no single concrete `T` that is both arguments' type and self-comparable. Kotlin's numeric types are each `Comparable` only to themselves, not across types, so mixed numeric arguments break self-referential `Comparable<T>` bounds. The fix is to make both arguments the same type, e.g. `mid(1.0, 2.0)`, or to redesign without the self-comparable constraint.
code
kotlin · 8 linesfun <T> mid(a: T, b: T): T where T : Number, T : Comparable<T> =
if (a <= b) a else b
fun main() {
println(mid(1, 2)) // Int -> 1
println(mid(1.0, 2.0)) // Double -> 1.0
// println(mid(1.0, 2)) // won't compile: Number is not Comparable<Number>
}go deeper
Recognizes both arguments must end up the same type.
Explains inference picks one T and that mixed types lack a common qualifying type.
Identifies the F-bounded Comparable<T> self-reference and that Number is not self-comparable, with a correct fix.
Generalizes about F-bounded polymorphism, why cross-type ordering is intentionally hard, and API alternatives (Comparator, explicit conversion).
## What the bounds demand `where T : Number, T : Comparable<T>` says the chosen `T` must (1) be a `Number` and (2) implement `Comparable<T>` — i.e. be comparable **to itself**, the same `T`. This self-reference (`Comparable<T>` where the type argument is `T` again) is the crux. ## Why `mid(1, 2)` works Both literals are `Int`. The compiler infers `T = Int`. Check the bounds: - `Int : Number` ✔ - `Int : Comparable<Int>` ✔ (each numeric type is comparable to its own type) So `T = Int` satisfies everything. ## Why `mid(1.0, 2)` fails Arguments are `Double` and `Int`. There is no single concrete `T` equal to both, so the compiler looks for a common supertype. Candidates: - `Number` — but `Number` does **not** implement `Comparable<Number>`. Fails bound (2). - `Comparable<*>` — too loose; cannot satisfy `Comparable<T>` with a concrete self-type. Because Kotlin's `Int`, `Double`, etc. are each `Comparable` **only to their own type** (`Int : Comparable<Int>`, not `Comparable<Number>`), there is no `T` that is both a supertype of `{Double, Int}` and self-comparable. Inference fails with no suitable type for `T`. ```kotlin fun <T> mid(a: T, b: T): T where T : Number, T : Comparable<T> = if (a <= b) a else b mid(1, 2) // T = Int -> OK mid(1.0, 2.0) // T = Double -> OK // mid(1.0, 2) // ERROR: no single T is both arg types AND Comparable<T> ``` ## The general lesson A **self-referential** bound `T : Comparable<T>` (the recursive/F-bounded pattern) means "comparable to exactly its own type." Combined with `Number`, it excludes mixing numeric types, because the only common supertype `Number` is not self-comparable. This is a deliberate consequence of multi-bound `where` requiring **one** type to satisfy **all** constraints at once. ## How to fix - Pass arguments of the **same** type: `mid(1.0, 2.0)`. - Or drop self-comparability and accept a `Comparator<T>`/`Comparable<*>` if cross-type comparison is genuinely needed (rare and lossy). - Or convert explicitly before calling: `mid(1.0, 2.toDouble())`.
- Why isn't `Number` an acceptable `T` here?`Number` satisfies the first bound but not `Comparable<Number>` — `Number` has no `compareTo`. The self-referential `Comparable<T>` bound rejects it.
- What is the name for the `T : Comparable<T>` pattern?A recursive or F-bounded constraint — the bound mentions the parameter itself, pinning comparison to the parameter's own type.
It's like requiring a key that fits both locks at once; two different keys (Double, Int) won't merge into one master key that's also self-comparable.
saying these in an interview costs you the question
- Saying mixed numeric args work because both are `Number`
- Not recognizing `Comparable<T>` is self-referential
- Claiming `Number` implements `Comparable`
- Suggesting the compiler will auto-widen Int to Double to satisfy the bound