Skip to content
AgileLogics
6 min read

Advanced Generics: Variance

Prerequisites: Generics, Inline functions, reified, and value classes

If String is a subtype of Any, is List<String> a subtype of List<Any>? The answer depends on variance — whether a generic type only produces values, only consumes them, or both.

out — producers are covariant

A type that only returns T can be declared out T. Then a Producer<String> can be used as a Producer<Any>:

fun printAll(items: List<Any>) {
    items.forEach { println(it) }
}

fun main() {
    val names: List<String> = listOf("Ada", "Linus")
    printAll(names)        // OK: List is declared List<out E>
}
Ada
Linus

This is safe because a read-only List can only hand out elements — nobody can put a number into your list of names through it.

MutableList<String> is not a MutableList<Any>: if it were, code could add an Int to a list that's supposed to contain only strings.

in — consumers are contravariant

A type that only accepts T can be declared in T. A Comparator<CharSequence> can compare any String, so it can be used where a Comparator<String> is needed:

fun main() {
    val byLength = Comparator<CharSequence> { a, b -> a.length - b.length }
    val words = mutableListOf("kiwi", "fig", "banana")
    words.sortWith(byLength)     // sortWith takes Comparator<in String>
    println(words)
}
[fig, kiwi, banana]

A handy memory aid: Producer out, Consumer in.

Declaring variance

interface Source<out T> {
    fun next(): T            // T only comes out
}

interface Sink<in T> {
    fun accept(item: T)      // T only goes in
}

The compiler checks you keep the promise: an out T type can't have a method that takes T as a parameter.

Star projection

When you don't know or care about the type argument, use *:

fun describe(items: List<*>) = "${items.size} items, first is ${items.firstOrNull()}"

fun main() {
    println(describe(listOf(1, 2, 3)))
    println(describe(listOf("a")))
}
3 items, first is 1
1 items, first is a

You can read from a List<*> (as Any?), but can't add to a MutableList<*>, because the element type is unknown.

Do you need this?

Mostly when designing libraries or generic utilities. In application code, knowing why List<String> fits where List<Any> is expected — and MutableList<String> doesn't — is usually enough.