Generics: in, out and star projection

Kotlin language Senior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Predict the output

Which line fails to compile?

fun main() {
    val strings: List<String> = listOf("a")
    val anys: List<Any> = strings
    val mutableStrings: MutableList<String> = mutableListOf("a")
    val mutableAnys: MutableList<Any> = mutableStrings
    println(anys + mutableAnys)
}
Options
Hint 1 of 1

Which interface lets you add elements?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Line 5: MutableList is invariant

Correct. If it compiled, you could add an Int to the list of Strings.

Why not the others?
  • B Line 3: List is invariant. List is declared List<out E>, so this is fine.
  • C Both lines 3 and 5. Only the mutable one is invariant.
  • D Neither. MutableList<String> isn't a MutableList<Any>.

30-second answer

out T means the type only produces T (covariant), so a Producer<String> can be used as a Producer<Any>. in T means it only consumes T (contravariant), so a Consumer<Any> can be used as a Consumer<String>. Kotlin's List<out E> is read-only, so List<String> is a subtype of List<Any>, but MutableList<String> is not a MutableList<Any>.

Practise saying it · 35 s. No recording, just a timer

Explain in, out and variance.

Say your answer out loud, then check which of these points you covered:

  • out = producer, covariant
  • in = consumer, contravariant
  • List is covariant, MutableList invariant
  • Star projection for unknown types

Deep dive

Declaration-site variance (interface Source<out T>) avoids Java's ? extends at every use.

Use-site variance (fun copy(from: Array<out Any>)) is there when the type itself is invariant.

Star projection (List<*>) means 'some type I don't know': you can read Any? but not write.

Remember 'producer out, consumer in'.

Common mistakes

  • Expecting MutableList<String> to be assignable to MutableList<Any>.
  • Marking a type out and then trying to accept T as a parameter.
  • Using Any instead of generics and casting everywhere.

Senior take & follow-ups

Give the MutableList counter-example; it proves you understand why variance exists.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why can't MutableList be covariant?

    Show hint

    You could add an Int to a list of Strings through the wider type.

  2. What does Comparator<in T> allow?

    Show hint

    A comparator of Any can sort a list of Strings.

Similar on iOS: Associated types and primary associated types Swift generics are invariant except for built-in collections; Kotlin declares variance with in and out.

Suggest a fix if something here is wrong or out of date.