The standard library declares Comparable<in T> and Iterable<out T>. What do the `in` and `out` modifiers on a generic interface's type parameter mean, and why are they placed where they are?
answer
- out = producer = covariant (Iterable<out T>)
- in = consumer = contravariant (Comparable<in T>)
- out only in return/val positions; in only in parameter positions
- Declaration-site: decided once, applies everywhere
- Compiler enforces position or errors
basics
~10 sout means T is only produced (returned), so the interface is covariant — Iterable<Cat> is an Iterable<Animal>. in means T is only consumed (passed in), so it's contravariant — Comparable<Animal> is a Comparable<Cat>.
solid answer
~40 s`in` and `out` are **declaration-site variance** modifiers on a type parameter. `out T` marks T **covariant**: the interface only *produces* T (returns it from methods, exposes it as a read-only `val`), so `Iterable<out T>` makes `Iterable<Cat>` a subtype of `Iterable<Animal>`. `in T` marks T **contravariant**: the interface only *consumes* T (takes it as a parameter), so `Comparable<in T>` makes `Comparable<Animal>` usable wherever a `Comparable<Cat>` is needed — if you can compare any Animal, you can compare a Cat. The compiler **enforces** position: an `out` parameter may not appear in an `in` (consuming) position, and vice versa, otherwise it reports a variance error. This is decided once at the declaration and benefits every use site, unlike Java's wildcards which are use-site only.
code
kotlin · 14 linesopen class Animal
class Cat : Animal()
fun feedAll(animals: Iterable<Animal>) { /* out T: producer */ }
fun main() {
val cats: List<Cat> = listOf(Cat())
feedAll(cats) // OK: Iterable<Cat> <: Iterable<Animal>
val animalCmp: Comparable<Animal> = object : Comparable<Animal> {
override fun compareTo(other: Animal) = 0
}
val catCmp: Comparable<Cat> = animalCmp // OK: in T contravariant
}go deeper
Recognizes that in/out relate to subtyping but may not name covariance/contravariance precisely.
Correctly maps out=producer=covariant and in=consumer=contravariant with examples.
Explains compiler position enforcement and why each modifier restricts where T may appear.
Contrasts declaration-site with use-site/Java wildcards and reasons about API design that keeps a parameter purely producing or consuming.
## The problem variance solves Generics are **invariant** by default: `Box<Cat>` is *not* a subtype of `Box<Animal>`, even though `Cat` is a subtype of `Animal`. That's safe but restrictive. **Variance** lets you relax this safely in one direction. ## Declaration-site variance: `out` and `in` Kotlin lets you annotate a type parameter **where the type is declared**, so the rule applies at every use site. ### `out` — covariance (producer) ```kotlin interface Iterable<out T> { operator fun iterator(): Iterator<T> // T only flows OUT } ``` - `out T` means the interface **only produces** T — returns it, never accepts it as a method parameter. - Effect: `Iterable<Cat>` **is a** `Iterable<Animal>` (covariant — subtyping flows the *same* way as T). - Mnemonic: **out = producer = covariant**. ### `in` — contravariance (consumer) ```kotlin interface Comparable<in T> { operator fun compareTo(other: T): Int // T only flows IN } ``` - `in T` means the interface **only consumes** T — accepts it as a parameter, never returns it. - Effect: `Comparable<Animal>` **is a** `Comparable<Cat>` (contravariant — subtyping flows the *opposite* way). If something can compare any `Animal`, it can certainly compare a `Cat`. - Mnemonic: **in = consumer = contravariant**. ## Why position is enforced The compiler checks that T appears only in legal positions: - `out T` may appear in **out positions** (return types, read-only `val`) but **not** in **in positions** (function parameters, `var` setters). Otherwise you could store a wrong subtype and read it back wrongly. - `in T` may appear in **in positions** but not as a return type. Violating this gives a compile error like *"Type parameter T is declared as 'out' but occurs in 'in' position."* ## Declaration-site vs use-site Kotlin's `in`/`out` on the declaration is decided **once** and applies everywhere — cleaner than Java's per-use-site wildcards (`? extends`, `? super`). Kotlin also supports **use-site variance** via *type projections* (`Array<out Animal>`) when a class can't be made variant overall, but the standard `Comparable`/`Iterable` use the declaration-site form. ## Summary table | Modifier | Role | Subtyping | Allowed positions | |----------|------|-----------|-------------------| | `out T` | producer | covariant (same direction) | return / val | | `in T` | consumer | contravariant (reversed) | parameter | | (none) | both | invariant | anywhere |
- What happens if you put an `out T` parameter in a method-argument position?The compiler rejects it: an out (covariant) parameter may only appear in out positions (return type, val). Use-site variance or invariance would be needed instead.
- How does declaration-site variance differ from Java's wildcards?Kotlin declares variance once at the type-parameter declaration so it applies at every use site; Java repeats `? extends`/`? super` at each use site.
out is a vending machine that only dispenses (you trust a Cat-dispenser as an Animal-dispenser); in is a shredder that only accepts (an any-Animal shredder can shred Cats).
saying these in an interview costs you the question
- Swapping the mnemonics: claiming `in` is covariant or `out` is contravariant
- Saying you can freely use an `out` parameter as a method argument
- Confusing declaration-site variance with use-site projections
- Thinking generics are covariant by default (they are invariant)