"out" in Kotlin: Understanding Covariance with Examples and Questions

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What's Behind the Word out in Kotlin? Answering Popular Questions

\n\n

If you've ever written generics in Kotlin and encountered a confusing compilation error, you've likely come across the word out. This small modifier sparks a lot of debate even among experienced developers. We've gathered the most common questions from Stack Overflow and turned them into an easy-to-understand guide. Today, you'll learn what out is, why it's needed, and how not to get confused by covariance.

\n\n

Let's figure it out once and for all — no boring theory, just live code and practice.

\n\n

Why Did Kotlin Invent out? (A Brief History)

\n\n

In Java, there's a wildcard — the question mark ? extends T. It means a collection can contain objects of type T or its subtypes. The problem is that Java doesn't prevent you from accidentally putting the wrong object into such a collection — you'll only get an error at runtime. Kotlin solved this problem at the type level.

\n\n

The keyword out in Kotlin is a declaration of covariance. If you write Source<out T>, it means: "I can read objects of type T from this Source, but I cannot write to it." The compiler strictly enforces this.

\n\n

Question 1: How is out Different from in?

\n\n

The most common question on Stack Overflow. Let's imagine a box:

\n\n
    \n
  • out — a box from which you can only take things out (produces).
  • \n
  • in — a box into which you can only put things (consumes).
  • \n
\n\n

Example:

\n\n
// Covariance: can read, but cannot write\ninterface Producer<out T> {\n    fun produce(): T\n}\n\n// Contravariance: can write, but cannot read\ninterface Consumer<in T> {\n    fun consume(item: T)\n}\n\nfun main() {\n    val stringProducer: Producer<String> = object : Producer<String> {\n        override fun produce(): String = "Hello"\n    }\n    \n    val anyProducer: Producer<Any> = stringProducer // Works! out allows this\n    println(anyProducer.produce())\n}\n
\n\n

Note: Producer<out T> allows assigning Producer<String> to a variable of type Producer<Any>. Without out, this would be impossible.

\n\n

Question 2: Why Does Kotlin Forbid fun set(value: T) with out?

\n\n

This is a classic trap. If you declare out, the compiler won't allow functions that accept T as a parameter. Why?

\n\n

Imagine you have a MutableList<out Number>. You could put an Int into it, but the compiler doesn't know what the actual list is. Maybe it's a MutableList<Double>? If writing were allowed, you'd break the types. out guarantees safety: you can only read.

\n\n
// Won't compile!\ninterface BadList<out T> {\n    fun get(index: Int): T  // OK\n    fun add(item: T)        // Error! T is in an in-position\n}\n
\n\n

Solution: if you need both reading and writing, use in and out separately, or use an invariant type (without a modifier).

\n\n

Question 3: How is out Related to the Java Wildcard ? extends?

\n\n

Direct correspondence: Source<out T> in Kotlin = Source<? extends T> in Java. But there's an important difference:

\n\n
    \n
  • In Java, wildcards are use-site variance.
  • \n
  • In Kotlin, out is declaration-site variance.
  • \n
\n\n

Example in Java:

\n\n
// Java: using wildcard when declaring a variable\nList<? extends Number> numbers = new ArrayList<Integer>();\nnumbers.a                

Blogs

Book Recommendations