For a class with a recursive (F-bounded) parameter like `class Node<T : Comparable<T>>`, what does `Node<*>` read at, and what subtle limitations does star projection impose here?
answer
- Recursive bound T:Comparable<T> -> read at Comparable<*>
- Each star = a distinct, unrelated unknown type
- Two Node<*> can't be compared to each other
- Capture <T:Comparable<T>> to relate two values
- Single-value ops may work via type-argument capture
basics
~20 sWith a self-referential bound like T : Comparable<T>, Node<*> projects T to that bound, but the bound mentions T again, so it's star-projected too: you read at Comparable<*>. You can read generically but can't safely compare two different starred nodes.
solid answer
~40 sWhen the upper bound itself references the type parameter (an **F-bound** / recursive bound), star projection substitutes the bound but must recursively project its own occurrence of `T`. For `class Node<T : Comparable<T>>`, `Node<*>` reads its `T` at `Comparable<*>` (not `Comparable<Any?>` and not a concrete type). The practical limitation: you cannot use the recursive capability across two independently-starred values. Two values `a: Node<*>` and `b: Node<*>` have *different unknown* `T`s, so `a.value.compareTo(b.value)` won't type-check — each star is a distinct existential. To regain the relation you must capture a real type parameter: `fun <T : Comparable<T>> compare(a: Node<T>, b: Node<T>)`. Star is fine for reading or single-element operations but forgets that two starred instances might share a type.
code
kotlin · 14 linesclass Node<T : Comparable<T>>(val value: T)
// star: read-only, can't relate two nodes
fun label(n: Node<*>): String = n.value.toString()
// captured T: can relate, compare, sort
fun <T : Comparable<T>> maxNode(a: Node<T>, b: Node<T>): Node<T> =
if (a.value >= b.value) a else b
fun main() {
val x = Node(3); val y = Node(7)
println(label(x)) // "3"
println(maxNode(x, y).value) // 7
}go deeper
Recognizes Node<*> means unknown element and that you can read it generically.
Knows star reads at the bound and that you generally can't write or compare freely.
Explains that two stars are distinct unknowns and switches to a captured <T> to compare.
Articulates F-bounds, recursive projection to Comparable<*>, type-argument capture, and the existential-per-star semantics.
## F-bounded (recursive) bounds A **recursive** or **F-bounded** type parameter is one whose upper bound mentions the parameter itself: `T : Comparable<T>`. It's the idiom for 'a type comparable to its own kind'. ## How star projects a recursive bound Star projection replaces the unknown `T` with its declared upper bound. But that bound, `Comparable<T>`, *contains* `T`. The compiler can't leave a free `T`, so it **recursively star-projects** the inner occurrence: - `Node<*>` reads its `T` as `Comparable<*>` — i.e., 'comparable to some unknown type', not `Comparable<Any?>` and not a concrete element type. ```kotlin class Node<T : Comparable<T>>(val value: T) fun peek(n: Node<*>) { val v: Comparable<*> = n.value // read type is Comparable<*> println(v) } ``` ## The key limitation: each star is a distinct existential Every star projection introduces its **own** fresh unknown type. Two separately-starred values are *not* known to share a type: ```kotlin fun broken(a: Node<*>, b: Node<*>) { // a.value.compareTo(b.value) // ERROR // a.value : Comparable<*> ; compareTo expects the SAME unknown type as a's T, // but b.value's unknown type is unrelated. } ``` This is fundamental: `*` *forgets* the type, so it can't reconnect two values. The recursive bound is exactly the case where you'd *want* to relate them, which is why star is so limiting here. ## Restoring the relation with a captured type parameter To compare two nodes, name a single `T` so both share it: ```kotlin fun <T : Comparable<T>> compare(a: Node<T>, b: Node<T>): Int = a.value.compareTo(b.value) // both T -> compareTo is well-typed ``` Here the type parameter *captures* the shared type, which `*` cannot. ## Self-operation within one starred value You can sometimes operate on a single starred value because the compiler captures its one unknown type locally (type-argument capture). With *two* stars there are two independent unknowns, so cross-comparison fails. ## Design takeaways - Use `Node<*>` for read-only/diagnostic access where you never relate two instances. - The moment you need to compare, sort, merge, or otherwise *relate* two values of the recursive type, switch to a captured `<T : Comparable<T>>` parameter. - This generalizes: any operation requiring two values to share the unknown type cannot be expressed with independent star projections — that's the defining boundary of `*` versus a named type variable.
- Why is the read type `Comparable<*>` rather than `Comparable<Any?>`?Because the bound `Comparable<T>` references the very parameter being eliminated; the compiler recursively star-projects that inner `T`, yielding `Comparable<*>`. `Comparable<Any?>` would falsely claim it's comparable to anything.
- Can you sort a `List<Node<*>>` by natural order?Not with `compareTo`, because each element's unknown `T` is independent, so comparing two elements doesn't type-check. You'd need a uniform `Comparator` on a type-erased key, or constrain to a shared `<T : Comparable<T>>`.
Two sealed boxes each labeled 'comparable to its own contents' — you can't compare box A's contents to box B's, because nothing guarantees they hold the same kind of thing; you'd need both stamped with the same serial number (a shared T).
saying these in an interview costs you the question
- Claiming `Node<*>` reads at `Comparable<Any?>` or `Any?`
- Thinking two `Node<*>` values can be compared directly
- Not recognizing each star is a separate existential type
- Believing a captured `<T>` and `*` are interchangeable here
- Overlooking that single-value operations may still type-check