Question

Understanding the `out` Keyword in Kotlin Generics

kotlingenerics

Question

I am having trouble understanding the out keyword in Kotlin, and I could not clearly find its meaning.

For example, what does this mean?

CopyList<out T>

Could someone explain what out does in Kotlin generics and when it is used?

Short Answer

By the end of this page, you will understand what out means in Kotlin generics, why it is used for covariance, and how it affects what a class or function can safely do with a type parameter. You will also see practical examples and common mistakes beginners make when reading or writing out T.

Concept

Kotlin's out keyword is used in generics to say that a type parameter is only produced and not consumed.

In simple terms:

  • out T means: "This generic type gives you values of type T"
  • It should not accept values of type T as input in unsafe ways
  • This makes the type covariant

Why this matters

Suppose Cat is a subtype of Animal.

If a generic type only produces values, then a Producer<Cat> can safely be used where a Producer<Animal> is expected, because every Cat is also an Animal.

That is exactly what out allows.

The core idea

class Box<out T>(private val value: T) {
    fun get(): T = value
}

Here, Box<out T> means:

  • Box can return a T
  • Box cannot safely accept a T as a writable input parameter in public API positions

Because of that, Kotlin knows this is safe:

val catBox: Box<Cat> = Box(Cat())
val animalBox: Box<Animal> = catBox

This works because Box only gives values out.

Covariance

out makes a type parameter covariant.

That means:

  • Producer<Cat> can be treated as Producer<Animal>
  • as long as the generic type only produces values of T

This is common in read-only structures such as:

  • List<out T>
  • result wrappers
  • data sources
  • iterators

Why Kotlin restricts usage

If Kotlin allowed both reading and writing with the same covariant type, type safety could break.

For example, imagine this were allowed:

class DangerousBox<out T>(var value: T)

If DangerousBox<Cat> were treated as DangerousBox<Animal>, someone could put a Dog into it. That would break the original Cat box.

So Kotlin prevents this kind of unsafe usage.

Mental Model

Think of out as a vending machine.

  • A vending machine gives items out
  • You can take items from it
  • But you do not put random items back into it

If a machine says it gives out Cat, then it is safe to treat it as a machine that gives out Animal, because every cat is an animal.

So:

  • out = output only
  • safe to read from
  • not safe to write into

A short memory trick:

  • out -> producer
  • in -> consumer

Another way to remember it:

  • Producer = out
  • Consumer = in

This is often called the PECS idea from generics:

  • Producer Extends -> like out
  • Consumer Super -> like in
Take Quiz

Syntax and Examples

Basic syntax

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

Here, T is marked with out, so Producer is only allowed to expose T as output.

Example with animals

open class Animal {
    fun speak() = println("animal sound")
}

class Cat : Animal()

class Cage<out T>(private val animal: T) {
    fun getAnimal(): T = animal
}

fun main() {
    val catCage: Cage<Cat> = Cage(Cat())
    val animalCage: Cage<Animal> = catCage

    val animal = animalCage.getAnimal()
    animal.speak()
}

Why this works

Cage<Cat> can safely become Cage<Animal> because the cage only returns animals. A Cat is still an Animal.

What is not allowed

class Cage<out T>(private val animal: T) {
    fun setAnimal(newAnimal: T) {
        // Error: T is declared as 'out' but occurs in 'in' position
    }
}

This is not allowed because setAnimal would consume a T, which breaks the rule for out.

Real Kotlin example

Kotlin's read-only list is declared like this conceptually:

interface List<out E>

That is why this is valid:

val cats: List<Cat> = listOf(Cat())
val animals: List<Animal> = cats

You can read from the list safely, but you cannot add elements to a read-only List.

Step by Step Execution

Consider this example:

open class Animal
class Cat : Animal()

class Source<out T>(private val value: T) {
    fun get(): T = value
}

fun main() {
    val catSource: Source<Cat> = Source(Cat())
    val animalSource: Source<Animal> = catSource
    val animal: Animal = animalSource.get()
    println(animal)
}

Step by step

1. Animal is the parent type

open class Animal
class Cat : Animal()
  • Cat inherits from Animal
  • So every Cat is also an Animal

2. Source<out T> is declared

class Source<out T>(private val value: T) {
    fun get(): T = value
}
  • out T means Source only produces T
  • The get() function returns T
  • This is safe for covariance

3. A Source<Cat> is created

val catSource: Source<Cat> = Source(Cat())
  • This source contains a Cat

4. It is assigned to Source<Animal>

val animalSource: Source<Animal> = catSource
  • This is allowed because Source is covariant with out T
  • Kotlin says: if it produces Cat, it can also be treated as something that produces Animal

5. We read the value

val animal: Animal = animalSource.get()
  • get() returns a Cat
  • A Cat fits into an Animal variable
  • So this is type-safe

That is the main purpose of out: allow safe substitution for producer-style generic types.

Real World Use Cases

out is useful when a type is mainly a source of values.

Common use cases

Read-only collections

fun printAnimals(animals: List<Animal>) {
    for (animal in animals) {
        println(animal)
    }
}

Because List is covariant, you can pass List<Cat> to a function expecting List<Animal>.

API response wrappers

sealed class Result<out T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
}

A Result<Cat> can be used where Result<Animal> is expected if the result only carries output data.

Data providers

interface Repository<out T> {
    fun getById(id: String): T
}

A repository that returns Cat objects can be used where a repository returning Animal objects is needed.

Iterators and streams

Anything that mainly emits values over time often benefits from covariance.

Take Quiz

Real Codebase Usage

In real projects, developers use out when designing APIs that are meant to be read-only or producer-oriented.

Common patterns

Read-only interfaces

A mutable class may have internal state, but the public interface exposes only output operations.

interface ReadOnlyBox<out T> {
    fun get(): T
}

This makes the API more flexible and safer.

Return-type wrappers

Wrappers like Result<T>, Response<T>, or Resource<T> often use out because callers mostly read the wrapped value.

Guarding against invalid writes

If a type should not accept outside values, marking it as out communicates that clearly in the type system.

Safer function parameters

A function that only reads from a generic source can accept covariant types more easily.

fun logAnimals(source: Source<Animal>) {
    println(source.get())
}

Then Source<Cat> can be passed in safely.

Design benefit

Using out is not just about syntax. It helps:

  • make APIs safer
  • express intent clearly
  • allow more flexible assignments
  • reduce unsafe casts

Common Mistakes

1. Thinking out means "outside"

It does not mean scope or visibility. It refers to the direction of type usage.

  • out = values go out of the generic type
  • in = values go into the generic type

2. Trying to use T as input in an out class

Broken example:

class Box<out T> {
    fun put(item: T) {
        // Error
    }
}

Why it fails:

  • item: T is an input position
  • out T only allows output positions

3. Assuming MutableList<Cat> can become MutableList<Animal>

This is unsafe.

val cats: MutableList<Cat> = mutableListOf(Cat())
// val animals: MutableList<Animal> = cats // Not allowed

If it were allowed, someone could add a different Animal that is not a Cat.

4. Confusing declaration-site variance with use-site variance

Kotlin can use out when declaring the generic type:

interface Source<out T>

It can also use projections at usage time:

fun copy(from: Array<out Any>) { }

These are related, but not exactly the same feature.

5. Forgetting that private/internal implementation details can differ

A class with out T may still store a T internally. The restriction is about how T appears in unsafe public input positions.

How to avoid mistakes

Ask this question:

  • Does this type mainly produce values of T? Use out.
  • Does it mainly consume values of T? Use in.
  • Does it do both? Usually use invariant T with no out or in.

Comparisons

ConceptMeaningSafe assignment exampleTypical use
out TCovariant, producer of TSource<Cat> -> Source<Animal>Read-only APIs, lists, results
in TContravariant, consumer of TConsumer<Animal> -> Consumer<Cat>Comparators, handlers, processors
plain TInvariantNo subtype substitutionMutable structures, read-write types

out vs in

interface Producer<out T> {
    fun produce(): T
}

interface Consumer<in T> {
    fun consume(item: T)
}
  • Producer gives values out
  • Consumer takes values in

List vs MutableList

TypeVariance styleWhy
List<out T>CovariantRead-only, safe to return items
MutableList<T>InvariantCan both read and write, so subtype substitution is unsafe

Java comparison

If you know Java, out is similar to:

? extends T

But Kotlin often expresses this more cleanly at the type declaration level.

Take Quiz

Cheat Sheet

Quick rules

  • out T means the generic type produces T
  • out makes the type parameter covariant
  • You can return T
  • You should not accept T in input positions

Safe idea

interface Source<out T> {
    fun get(): T
}

Unsafe idea

interface Box<out T> {
    fun set(value: T) // Not allowed
}

Assignment rule

If Cat : Animal, then with out:

Source<Cat> -> Source<Animal>

Memory trick

  • out = producer
  • in = consumer

Common examples

  • List<out T>
  • Result<out T>
  • Iterator<out T>

When not to use out

Do not use out when the type both:

  • returns T
  • accepts T

In that case, keep it invariant.

Related syntax

Use-site projection:

fun readAll(items: Array<out Any>) { }

This means the function can safely read from items as Any.

FAQ

What does out mean in Kotlin?

out marks a generic type parameter as covariant. It means the type is used for output only, so the generic type can safely produce values of that type.

Why is List declared with out in Kotlin?

Because a read-only list only provides items. It does not allow adding new ones, so covariance is safe.

What is the difference between out and in in Kotlin?

  • out is for producers
  • in is for consumers

Use out when values come from the type, and in when values go into the type.

Can I use a type parameter with both input and output if it is marked out?

No. If a type parameter is marked out, Kotlin restricts it from being used in unsafe input positions.

Is out the same as Java extends in generics?

It is similar to Java's ? extends T in many cases. Both express covariance.

Why is MutableList not covariant?

Because it allows both reading and writing. If MutableList<Cat> were treated as MutableList<Animal>, someone could add a Dog, which would be unsafe.

What does CopyList<out T> mean?

It means the generic type parameter T is covariant. CopyList is expected to produce T values safely, rather than consume them as writable inputs.

Take Quiz

Mini Project

Description

Build a small Kotlin example that models a read-only animal provider. This project demonstrates why out is useful when a generic type only returns values and should support safe subtype substitution.

Goal

Create a covariant generic provider that can return Cat objects and still be used where an Animal provider is expected.

Requirements

  • Define a parent class Animal and at least one subclass Cat.
  • Create a generic interface or class using out T.
  • Add a function that returns a value of type T.
  • Show that an instance with Cat can be assigned to a variable expecting Animal.
  • Print or use the returned value to prove the assignment works.
Take Quiz

Keep learning

Related questions

View more