What does the `in` modifier mean on a type parameter such as `Comparable<in T>`, and how does subtyping behave for an `in` type?
answer
- in = consumer = contravariant
- subtyping REVERSED: Sink<Animal> <: Sink<Cat>
- Comparable<in T> in the stdlib
- T allowed only as function parameters
- PECS: in = Consumer
basics
~10 sin makes the type parameter contravariant: the type only consumes (accepts) values of T, never returns them. Subtyping is reversed — a Comparable<Animal> can be used where a Comparable<Cat> is expected.
solid answer
~40 s`in T` marks T as **contravariant**. T may appear only in `in` positions — function parameters — so the class is a pure consumer of T. Subtyping is **reversed** relative to T: if `Cat : Animal`, then `Comparable<Animal>` is a subtype of `Comparable<Cat>`. That feels backwards but is sound: something that can compare any Animal can certainly compare a Cat. The standard library declares `public interface Comparable<in T> { operator fun compareTo(other: T): Int }`, and consumers like `kotlin.Function`'s parameter types are contravariant. Because of contravariance, a `Comparable<Number>` instance satisfies a slot expecting `Comparable<Int>`. The compiler enforces, at the declaration, that T never appears as a return type or `val`; if it did (an out position) the `in` annotation would be rejected.
code
kotlin · 10 linesinterface Sink<in T> {
fun accept(item: T)
}
fun feedCats(sink: Sink<Cat>) { sink.accept(Cat()) }
val animalSink: Sink<Animal> = object : Sink<Animal> {
override fun accept(item: Animal) { /* ... */ }
}
feedCats(animalSink) // Sink<Animal> is a subtype of Sink<Cat>go deeper
Recognizes in = consumer and that Comparable uses it.
Correctly states the reversed subtyping and the in-position restriction.
Explains soundness of contravariance and ties it to function-parameter variance.
Designs consumer interfaces (callbacks, comparators) with in to maximize the set of accepted concrete types in a public API.
## What `in` declares `in` means **contravariance**. The type parameter is used only as **in**put — values flow *into* the object as function arguments — and are never returned. ```kotlin interface Sink<in T> { fun accept(item: T) // T in an IN position (parameter) — allowed // fun produce(): T // T in an OUT position (return) — COMPILE ERROR } ``` ## Reversed subtyping With `in`, the subtyping relation is **flipped** compared to the type argument: - `Cat` is a subtype of `Animal` - therefore `Sink<Animal>` **is a subtype of** `Sink<Cat>` (note the reversal) ```kotlin val animalSink: Sink<Animal> = ... val catSink: Sink<Cat> = animalSink // OK: contravariance catSink.accept(Cat()) // safe: a Cat IS an Animal, animalSink can take it ``` This is sound: a consumer that accepts **any** Animal can safely be handed a Cat. ## Standard library example: `Comparable` ```kotlin public interface Comparable<in T> { public operator fun compareTo(other: T): Int } ``` Because T is contravariant, a comparator/comparable for a broader type works where a narrower one is required: ```kotlin fun sortNumbers(c: Comparable<Int>) { /* ... */ } val anyNumberCmp: Comparable<Number> = ... sortNumbers(anyNumberCmp) // Comparable<Number> <: Comparable<Int> ``` ## Mnemonic: PECS-flavored intuition - **out** → **P**roducer → covariant (subtyping preserved) - **in** → **C**onsumer → contravariant (subtyping reversed) ## The position check At the declaration, the compiler verifies T only appears in `in` positions (parameters). If you write `fun produce(): T` in a `Sink<in T>`, you get *"Type parameter T is declared as 'in' but occurs in 'out' position"*. The check is **once, at the declaration site**, so every usage is automatically safe. ## Terms - **Contravariance**: subtyping reversed (`A : B` ⇒ `G<B> : G<A>`). - **Consumer**: a type that only takes T as input. - **in position**: function parameter type, `var` setter parameter.
- Why does contravariance reverse the subtype direction?Because safety flows from the parameter side. A consumer of all Animals accepts every Cat, so it can substitute for a Cat-consumer; the broader-input type is the more substitutable (sub)type.
- Can a type parameter be both `in` and `out`?No. A single parameter is either covariant (`out`), contravariant (`in`), or invariant (neither). Being both would require T in both positions, which is invariant by definition.
An in type is a shredder: a shredder that destroys any Animal will happily destroy a Cat, so it qualifies as a 'Cat shredder'.
saying these in an interview costs you the question
- Saying `in` preserves subtyping like `out` does
- Claiming `in` types can return T from methods
- Mixing up producer/consumer (calling `in` a producer)
- Thinking the reversal is a Kotlin quirk rather than soundness
- Asserting a parameter can be declared both `in` and `out`