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 Tmeans: "This generic type gives you values of typeT"- It should not accept values of type
Tas 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:
Boxcan return aTBoxcannot safely accept aTas 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 asProducer<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-> producerin-> 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
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()
Catinherits fromAnimal- So every
Catis also anAnimal
2. Source<out T> is declared
class Source<out T>(private val value: T) {
fun get(): T = value
}
out TmeansSourceonly producesT- The
get()function returnsT - 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
Sourceis covariant without T - Kotlin says: if it produces
Cat, it can also be treated as something that producesAnimal
5. We read the value
val animal: Animal = animalSource.get()
get()returns aCat- A
Catfits into anAnimalvariable - 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.
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 typein= 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: Tis an input positionout Tonly 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? Useout. - Does it mainly consume values of
T? Usein. - Does it do both? Usually use invariant
Twith nooutorin.
Comparisons
| Concept | Meaning | Safe assignment example | Typical use |
|---|---|---|---|
out T | Covariant, producer of T | Source<Cat> -> Source<Animal> | Read-only APIs, lists, results |
in T | Contravariant, consumer of T | Consumer<Animal> -> Consumer<Cat> | Comparators, handlers, processors |
plain T | Invariant | No subtype substitution | Mutable structures, read-write types |
out vs in
interface Producer<out T> {
fun produce(): T
}
interface Consumer<in T> {
fun consume(item: T)
}
Producergives values outConsumertakes values in
List vs MutableList
| Type | Variance style | Why |
|---|---|---|
List<out T> | Covariant | Read-only, safe to return items |
MutableList<T> | Invariant | Can 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.
Cheat Sheet
Quick rules
out Tmeans the generic type producesToutmakes the type parameter covariant- You can return
T - You should not accept
Tin 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= producerin= 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?
outis for producersinis 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.
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
Animaland at least one subclassCat. - Create a generic interface or class using
out T. - Add a function that returns a value of type
T. - Show that an instance with
Catcan be assigned to a variable expectingAnimal. - Print or use the returned value to prove the assignment works.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.