skip to content

Declaration-Site Variance (in / out)

Kotlin lets you declare variance once on the class — out for producers like List<out E>, in for consumers like Comparable<in T> — instead of at every use site. The compiler then checks that the parameter really only appears in the allowed positions.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

In Kotlin, what does the `out` modifier on a type parameter mean, as in `interface Source<out T>`, and how does it affect assignment between generic types?

level: juniorimportance: must knowfreq 70%

answer

  1. out = producer = covariant
  2. List<out E> is read-only and assignable up
  3. subtype direction preserved: Source<Cat> <: Source<Animal>
  4. T allowed only in return/val positions
  5. annotate once at the declaration

basics

~10 s

out makes the type parameter covariant: the type only produces (returns) values of T, never consumes them. So a Source<Cat> can be used where a Source<Animal> is expected, because every Cat is an Animal.

solid answer

~40 s

Declaring `interface Source<out T>` marks T as covariant. It tells the compiler T appears only in `out` positions — return types of functions and `val` properties — so the class is a pure producer of T. Because of that safety guarantee, subtyping is preserved in the same direction as T: if `Cat` is a subtype of `Animal`, then `Source<Cat>` is a subtype of `Source<Animal>`. Kotlin's `List<out E>` is the canonical example: `List<String>` is assignable to `List<Any>`. The compiler enforces this once at the declaration (declaration-site variance), so every call site benefits without extra annotations. If you tried to add `fun consume(item: T)`, the compiler rejects it because T would then appear in an `in` position.

code

kotlin · 10 lines
kotlin
interface Source<out T> {
    fun next(): T
}

fun useAnimals(src: Source<Animal>) { /* ... */ }

val catSource: Source<Cat> = object : Source<Cat> {
    override fun next() = Cat()
}
useAnimals(catSource) // works: Source<Cat> is a subtype of Source<Animal>

go deeper

for a junior

Knows out = covariant producer and that List<String> is usable as List<Any>.

for a middle

Explains the out-position restriction and why MutableList can't be covariant.

for a senior

Articulates declaration-site vs use-site and soundness reasoning behind the position check.

for a principal

Connects variance to API design — choosing read-only covariant interfaces to widen accepted types across a public surface.

## What `out` declares `out` on a type parameter means **covariance**. Read it literally: the type parameter is only used as **out**put — values of that type come *out of* the object (return values), they are never passed *in*. ```kotlin interface Source<out T> { fun next(): T // T in an OUT position (return) — allowed // fun put(item: T) // T in an IN position (parameter) — COMPILE ERROR } ``` ## Why this enables subtyping Normally generics are **invariant**: `Box<Cat>` and `Box<Animal>` are unrelated types even though `Cat : Animal`. Variance changes that. With `out`, subtyping flows in the **same** direction as the type argument: - `Cat` is a subtype of `Animal` - therefore `Source<Cat>` **is a subtype of** `Source<Animal>` ```kotlin val cats: Source<Cat> = ... val animals: Source<Animal> = cats // OK because of `out` val a: Animal = animals.next() // safe: a Cat IS an Animal ``` This is sound because everything you can get out of `animals` is at least an `Animal`. ## The standard library example `kotlin.collections.List` is declared `public interface List<out E>`. That is why this compiles: ```kotlin val strings: List<String> = listOf("a", "b") val anys: List<Any> = strings // List<String> <: List<Any> ``` `List` is read-only (no `add`), so E never appears in an `in` position — covariance is safe. `MutableList<E>` is **invariant** (no `out`) precisely because `add(e: E)` consumes E. ## Declaration-site The key Kotlin idea: you write `out` **once**, at the class/interface declaration. Every use of `Source<…>` then automatically gets covariant behavior. The compiler verifies, at the declaration, that T is only ever used in safe (out) positions. ## Terms - **Covariance**: subtyping preserved (`A : B` ⇒ `G<A> : G<B>`). - **Producer**: a type that only returns T. - **out position**: function return type, `val` property type.

  • Why is `MutableList<E>` not declared with `out`?
    Because `add(element: E)` and `set(index, element: E)` put E into an `in` position. Marking it `out` would let you treat a `MutableList<Cat>` as `MutableList<Animal>` and add a Dog, breaking type safety.
  • Can a covariant `out` type have a function parameter of type T at all?
    Not in a normal parameter position. The compiler rejects T in `in` positions. You can sometimes work around with `@UnsafeVariance`, but that disables the safety check and is rarely appropriate.

An out type is like a vending machine you can only take items from: if it dispenses Cats, it certainly dispenses Animals.

saying these in an interview costs you the question

  • Saying `out` means the parameter is optional or nullable
  • Claiming `out` makes `Source<Animal>` a subtype of `Source<Cat>` (direction reversed)
  • Thinking `out` is only a runtime hint with no compile-time effect
  • Believing you can still pass T as a function argument in an `out` class
  • Confusing `out` with the `out` keyword for output function parameters from other languages

context

open as a page

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%

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.

open as a page

Explain the compiler's position check for declaration-site variance. What counts as an `out` position versus an `in` position, and what error do you get when you violate it?

level: middleimportance: should knowfreq 45%

basics

~20 s

The compiler checks, at the class declaration, that an out parameter is only used in return positions and an in parameter only in argument positions. Break the rule and it reports the parameter occurring in the wrong position.

open as a page

When designing a generic interface, how do you decide whether a type parameter should be `out`, `in`, or invariant? Walk through the trade-offs with concrete examples.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Look at how the type parameter is used. If the type only returns it, mark it out. If the type only accepts it, mark it in. If it does both, leave it invariant. The choice widens what callers can pass.

open as a page

How does declaration-site variance interact with private members and the `@UnsafeVariance` annotation? Give a real standard-library example where the position check is deliberately bypassed.

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Private members are skipped by the variance check because they aren't part of the public type. @UnsafeVariance lets you mark one spot to ignore the check on purpose. Kotlin's List<out E>.contains uses it because the method only reads.

open as a page