0Pricing
Kotlin Academy · Lesson

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

  1. Generic Functions and Classes
  2. Declaration-Site Variance
  3. Use-Site Variance
  4. Generic Constraints
← Back to Kotlin Academy