← Back to Kotlin Course | Chapter 11: Generics | Lesson 3 of 6

Variance: in and out

in and out tell Kotlin whether a generic placeholder is only ever going to be received, only ever handed out, or both, so it can allow safer substitutions.

The Problem Without Variance

By default, generic types are invariant, meaning Box<Dog> is not automatically treated as a Box<Animal> even if Dog is a subtype of Animal, since that could otherwise let incompatible types be inserted unsafely.

Example: The Problem Without Variance

markup
open class Animal(val name: String)
class Dog(name: String) : Animal(name)

class Box<T>(val item: T)

fun main() {
    val dogBox: Box<Dog> = Box(Dog("Rex"))
    println(dogBox.item.name)
}

Covariance with out

Marking a type parameter out allows a Producer<Sub> to be used wherever a Producer<Super> is expected, since the type only ever appears as something being produced (returned), which is always safe.

Example: Covariance with out

markup
open class Animal(val name: String)
class Dog(name: String) : Animal(name)

class Producer<out T>(private val item: T) {
    fun produce(): T = item
}

fun printAnimalName(producer: Producer<Animal>) {
    println(producer.produce().name)
}

fun main() {
    val dogProducer: Producer<Dog> = Producer(Dog("Rex"))
    printAnimalName(dogProducer)
}

Contravariance with in

Marking a type parameter in allows a Consumer<Super> to be used wherever a Consumer<Sub> is expected, since the type only ever appears as something being accepted (a parameter), which is safe in the opposite direction.

Example: Contravariance with in

markup
open class Animal(val name: String)
class Dog(name: String) : Animal(name)

class Consumer<in T> {
    fun consume(item: T) {
        println("Consuming an item")
    }
}

fun feedDog(consumer: Consumer<Dog>) {
    consumer.consume(Dog("Rex"))
}

fun main() {
    val animalConsumer: Consumer<Animal> = Consumer()
    feedDog(animalConsumer)
}

Kotlin's Built-in List<out T>

The standard library's read-only List<out T> is declared covariant, which is why a List<Dog> can be passed directly wherever a List<Animal> is expected.

Example: Kotlin's Built-in List<out T>

markup
open class Animal(val name: String)
class Dog(name: String) : Animal(name)

fun printNames(animals: List<Animal>) {
    for (animal in animals) println(animal.name)
}

fun main() {
    val dogs: List<Dog> = listOf(Dog("Rex"), Dog("Fido"))
    printNames(dogs)
}
Common Mistakes
  1. Using an invariant generic type where a covariant (out) or contravariant (in) one would allow more flexible, still-safe code.
  2. Marking a type parameter out and then trying to use it as a function parameter type, which the compiler forbids since out restricts a type to output positions only.
  3. Confusing out (covariant, producer) with in (contravariant, consumer); they allow opposite kinds of substitutions.
Chapter Summary
  • out T marks a type parameter as covariant, meaning it can only appear in output (return) positions, and allows Producer<Sub> to be used where Producer<Super> is expected.
  • in T marks a type parameter as contravariant, meaning it can only appear in input (parameter) positions, and allows Consumer<Super> to be used where Consumer<Sub> is expected.
  • Without in/out (invariant), Container<Sub> is not automatically substitutable for Container<Super>, even if Sub is a subtype.
  • Kotlin's built-in List<out T> is covariant (read-only), while a hypothetical pure consumer interface would typically be contravariant.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.