skip to content

What does the `in` modifier mean on a type parameter such as `Comparable<in T>`, and how does subtyping behave for an `in` type?

level: middleimportance: must knowfreq 60%

answer

  1. in = consumer = contravariant
  2. subtyping REVERSED: Sink<Animal> <: Sink<Cat>
  3. Comparable<in T> in the stdlib
  4. T allowed only as function parameters
  5. PECS: in = Consumer

basics

~10 s

in 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 lines
kotlin
interface 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

for a junior

Recognizes in = consumer and that Comparable uses it.

for a middle

Correctly states the reversed subtyping and the in-position restriction.

for a senior

Explains soundness of contravariance and ties it to function-parameter variance.

for a principal

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`

context