Declaration-Site Variance
in and out.
Declaration-Site Variance is a free Kotlin Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Variance Problem
Is a List<String> a kind of List<Any>? Sometimes yes, sometimes no. Variance describes how generic types relate when their type parameters relate. Kotlin uses out and in to express this.
Invariance by Default
By default generics are invariant: Box<String> is NOT a Box<Any>, even though String is a subtype of Any. This prevents unsafe operations.
class Box<T>(val value: T)
fun main() {
val strBox = Box("hi")
// val anyBox: Box<Any> = strBox // would not compile
println(strBox.value)
}Covariance with out
Marking a type parameter out makes it covariant: Producer<String> becomes a subtype of Producer<Any>. The type can only appear in output positions (return types).
class Producer<out T>(private val item: T) {
fun produce(): T = item
}
fun main() {
val strProducer: Producer<String> = Producer("hello")
val anyProducer: Producer<Any> = strProducer
println(anyProducer.produce())
}Why out Is Safe
If a class only produces (returns) T and never consumes it, treating a Producer<String> as a Producer<Any> is safe, because every String it returns is also an Any.
out in the Standard Library
Kotlin's read-only List<out E> is covariant, which is why a List<String> can be used where a List<Any> is expected.
fun printAll(items: List<Any>) {
items.forEach { println(it) }
}
fun main() {
val names: List<String> = listOf("Ann", "Bob")
printAll(names)
}Contravariance with in
Marking a type parameter in makes it contravariant: Consumer<Any> becomes a subtype of Consumer<String>. The type can only appear in input positions (parameters).
class Consumer<in T> {
fun consume(item: T) {
println("Consumed $item")
}
}
fun main() {
val anyConsumer: Consumer<Any> = Consumer()
val strConsumer: Consumer<String> = anyConsumer
strConsumer.consume("hi")
}Why in Is Safe
A consumer that accepts Any can certainly accept a String. So a Consumer<Any> can stand in wherever a Consumer<String> is needed.
The PECS Mnemonic
Remember Producer Extends, Consumer Super, or in Kotlin terms: out for producers (output), in for consumers (input). A type that only outputs T uses out; one that only inputs T uses in.
Combining in and out
A type parameter used for both input and output (like in a mutable list) must stay invariant. Function types show both: parameters are in, the return is out.
fun main() {
val f: (Number) -> Int = { it.toInt() }
val g: (Int) -> Number = f
println(g(3.0.let { Number::class; 5 }))
}Declaration-Site Means One Place
Declaration-site variance is declared once, on the class itself with in/out. Every use of the class then gets the variance automatically, unlike Java where you repeat wildcards at each use.
Choosing in or out
Ask: does this class only produce T (use out), only consume T (use in), or both (leave invariant)? Matching the modifier to the role makes your generic types flexible and safe.
Quick Check
What does the out modifier indicate?
Recap
Generics are invariant by default. Use out for covariance when a type only produces values, and in for contravariance when it only consumes values. Remember producers use out, consumers use in. Next you will apply variance at the point of use with type projections.
Frequently asked questions
Is the “Declaration-Site Variance” lesson free?
Yes — the full text of “Declaration-Site Variance” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.
What will I learn in “Declaration-Site Variance”?
in and out. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Kotlin Academy?
No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Declaration-Site Variance” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Kotlin Academy lesson?
Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Generic Functions and Classes
- Declaration-Site Variance
- Use-Site Variance
- Generic Constraints