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