← 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.
Syntax
kotlin
class Producer<out T>(val value: T)     // covariant: only produces T
class Consumer<in T> {                  // contravariant: only consumes T
    fun consume(item: T) { }
}

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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)

// Define a class `Box`
class Box<T>(val item: T)

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `dogBox` (a `Box<Dog>`), set to `Box(Dog("Rex"))`
    val dogBox: Box<Dog> = Box(Dog("Rex"))
    // Print `dogBox.item.name` to the console, with a trailing newline
    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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)

// Define a class `Producer`
class Producer<out T>(private val item: T) {
    // Define the function `produce` with no parameters and returning a `T`
    fun produce(): T = item
}

// Define the function `printAnimalName` taking `producer`
fun printAnimalName(producer: Producer<Animal>) {
    // Print `producer.produce().name` to the console, with a trailing newline
    println(producer.produce().name)
}

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `dogProducer` (a `Producer<Dog>`), set to `Producer(Dog("Rex"))`
    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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)

// Define a class `Consumer`
class Consumer<in T> {
    // Define the function `consume` taking `item`
    fun consume(item: T) {
        // Print "Consuming an item" to the console, with a trailing newline
        println("Consuming an item")
    }
}

// Define the function `feedDog` taking `consumer`
fun feedDog(consumer: Consumer<Dog>) {
    consumer.consume(Dog("Rex"))
}

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `animalConsumer` (a `Consumer<Animal>`), set to `Consumer()`
    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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)

// Define the function `printNames` taking `animals`
fun printNames(animals: List<Animal>) {
    // Loop over `animals) println(animal.name`, binding each item to `animal`
    for (animal in animals) println(animal.name)
}

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `dogs` (a `List<Dog>`), set to `listOf(Dog("Rex"), Dog("Fido"))`
    val dogs: List<Dog> = listOf(Dog("Rex"), Dog("Fido"))
    printNames(dogs)
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
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.