Generic Variance (in / out)
Variance controls whether a generic type like Box<Dog> can stand in for Box<Animal> , using out for producers and in for consumers.
Learn Generic Variance (in / out) in our free Kotlin course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll learn covariance with out , contravariance with in , why MutableList is invariant, and the star projection * .
What You'll Learn in This Lesson
1️⃣ out: Covariant Producers
Marking a type parameter out T promises the class only ever produces T (returns it), never consumes it. That makes it safe for a Box<Dog> to be used as a Box<Animal> — every dog you read out is also an animal. This is why List is declared List<out E> .
2️⃣ in: Contravariant Consumers
Marking in T promises the class only ever consumes T (takes it as input), never returns it. This reverses the direction: a Sink<Animal> can be used where a Sink<Dog> is expected, because anything that handles any animal can handle a dog. This is why Comparator is Comparator<in T> .
3️⃣ Invariance and the Star Projection
A type that both produces and consumes — like MutableList<T> — can't be made in or out safely, so it stays invariant and its arguments must match exactly. When you don't care about the element type at all, use the star projection List<*> for a safe, read-only view.
Your turn. Fill in the ___ blank, then run and compare.
Use out so a Source<Int> works where a Source<Number> is required.
📋 Quick Reference — Variance
Practice quiz
What does variance control in generics?
- How fast a generic compiles
- Whether Box<Dog> can stand in for Box<Animal>
- How many type parameters are allowed
- Whether a type can be null
Answer: Whether Box<Dog> can stand in for Box<Animal>. Variance governs the subtype relationship between Box<Dog> and Box<Animal>.
By default, Kotlin generics are…
- Covariant
- Contravariant
- Invariant
- Nullable
Answer: Invariant. Without an annotation, generics are invariant — no subtype relationship.
What does the 'out' modifier make a type parameter?
- A covariant producer
- A contravariant consumer
- Invariant
- Reified
Answer: A covariant producer. out marks a covariant producer: T only flows out (is returned).
Which standard type is declared Comparator<in T>?
- List
- Box
- Comparator
- Map
Answer: Comparator. Comparator is contravariant: Comparator<in T> consumes values of T.
With 'in T', which substitution is allowed?
- Sink<Dog> where Sink<Animal> is expected
- Nothing can substitute
- Sink<Animal> where Sink<Dog> is expected
- Only exact matches
Answer: Sink<Animal> where Sink<Dog> is expected. in reverses the relationship: a Sink<Animal> works where Sink<Dog> is needed.
Why must MutableList stay invariant?
- It is immutable
- It both produces and consumes T
- It has no type parameter
- It only consumes T
Answer: It both produces and consumes T. MutableList reads (produces) and adds (consumes) T, so neither relaxation is safe.
What does the star projection List<*> give you?
- A writable list of Any
- A safe read-only view of an unknown element type
- A null list
- A covariant MutableList
Answer: A safe read-only view of an unknown element type. List<*> is a list of some unknown type, usable as a read-only view.
If 'out T' occurs as a function parameter (in position), what happens?
- It works fine
- It is silently ignored
- It auto-converts to in
- It is a compile error
Answer: It is a compile error. A producer can't consume T, so taking out T as a parameter is a compile error.
How does Java's PECS map to Kotlin?
- Producer -> out, Consumer -> in
- Producer -> in, Consumer -> out
- Both map to invariant
- Both map to *
Answer: Producer -> out, Consumer -> in. Producer Extends -> out; Consumer Super -> in.
Where does declaration-site variance put the annotation?
- At each usage site
- On the class itself, like class Box<out T>
- On the main function
- Only in Java code
Answer: On the class itself, like class Box<out T>. Declaration-site variance annotates the class once, e.g. class Box<out T>.