skip to content

Variance & Projections

Why List<String> can be used where List<Any> is expected but MutableList<String> cannot: declaration-site in and out, use-site projections, and the star projection. Variance is the single hardest generics topic and interviewers know it.

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

explore

questions

20

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 star projection `List<*>` mean in Kotlin, and what can you safely do with such a value?

level: juniorimportance: must knowfreq 70%

basics

~20 s

List<*> means a list of some unknown element type. You can read items as Any? and call type-agnostic members like size, but you cannot safely add a specific element because the real type is unknown.

open as a page

What is a use-site projection in Kotlin, and why do you need Array<out T> when copying from a source array even though Array is invariant?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Array<T> is invariant, so an Array<Int> is not an Array<Any>. Writing Array<out T> at a parameter says 'I only read from this array', which lets you pass arrays of subtypes safely.

open as a page

How do Kotlin's `out` and `in` variance modifiers map onto Java's `? extends` and `? super` wildcards, and how does this relate to the PECS rule?

level: juniorimportance: must knowfreq 70%

basics

~10 s

out means a type only produces values, like Java's ? extends (a producer). in means a type only consumes values, like Java's ? super (a consumer). PECS: Producer Extends, Consumer Super.

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 how the star projection maps to bounds: for `Foo<out T : TUpper>` and `Foo<in T>`, what does `Foo<*>` become for reading and writing?

level: middleimportance: must knowfreq 60%

basics

~20 s

For an out parameter, Foo<*> reads as the parameter's upper bound (Any? if none). For an in parameter, Foo<*> only accepts Nothing for writes, so you effectively can't write. Star = read at upper bound, write nothing.

open as a page

For a value of type Array<out Number>, which Array members can you call and which are forbidden, and why?

level: middleimportance: must knowfreq 45%

basics

~10 s

You can read: get returns Number, and size works. You cannot call set, because the real element type is unknown and writing a Number could be wrong. Out makes it read-only for elements.

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

Explain Array<in T> (a use-site 'in' projection): when would you use it, and what does it do to get and set?

level: middleimportance: should knowfreq 35%

basics

~20 s

Array<in T> means 'I only write T into this array'. You can call set with a T, and you can pass arrays of supertypes. But reading gives back Any?, because the array may actually hold a broader type.

open as a page

What Java wildcard does Kotlin's star-projection (`List<*>`) map to, and how does it differ from a raw type?

level: middleimportance: should knowfreq 50%

basics

~10 s

List<*> maps to Java's unbounded wildcard List<?>. It means 'a list of some specific but unknown type'. Unlike a raw type List, it stays type-safe: you can read Any? but can't add elements.

open as a page

Kotlin offers both declaration-site variance and use-site projections. How do each map to Java's wildcard model, and why does Kotlin prefer declaration-site?

level: middleimportance: should knowfreq 45%

basics

~20 s

Java has only use-site wildcards (? extends/? super at each usage). Kotlin adds declaration-site variance — out/in written once on the class — and also supports use-site projections (List<out T>) that map straight to wildcards.

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 star projection interact with runtime type checks (`is`/`as`) and generic erasure? Why does `x is List<*>` compile while `x is List<String>` does not?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Kotlin erases generic type arguments at runtime, so you can't check is List<String> — the element type isn't there to check. The star projection is List<*> only checks that it's a List at all, which is exactly what's verifiable after erasure.

open as a page

When should you use a star projection `List<*>` versus an explicit use-site projection like `List<out Number>` or a generic type parameter `<T>`?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Use * only when you truly don't know or need the type — pure read/passthrough/counting. Use an explicit projection (out Number) when you need a useful upper bound. Use a generic <T> when callers need to relate input and output types.

open as a page

Walk through how the compiler keeps Array<out T> sound: what type does it assign to set's parameter, what is the type of get under Array<in T>, and how does this preserve type safety?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Under Array<out T> the compiler types set's value as Nothing, so no value can be written. Under Array<in T> it types get's result as Any?, so reads are imprecise. Both rules block the operations that could store the wrong type.

open as a page

When should you reach for a use-site projection (Array<out T>) versus declaration-site variance, and why can't Array itself be declared covariant?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Declare variance on the class when the type is always a producer or always a consumer of T. Use a use-site projection when the class is invariant because it both reads and writes T, like Array, and you only need one direction at a specific call site.

open as a page

When does the Kotlin compiler emit Java wildcards in bytecode for a declaration-site-variant type, and what does `@JvmSuppressWildcards` / `@JvmWildcard` do?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Kotlin auto-generates Java wildcards when an out/in type appears in a method signature, so Java callers get correct variance. @JvmSuppressWildcards removes them; @JvmWildcard forces one where Kotlin would omit it.

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

When Kotlin calls a Java API that uses wildcards like `List<? extends Number>`, how does Kotlin see that type, and what pitfalls arise around platform types and variance?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Kotlin translates Java's ? extends Number into out Number and ? super Number into in Number. Java types without nullability info become platform types (Number!), so you must be careful about nulls and what you can read or write.

open as a page

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?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

With 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.

open as a page