Kotlin out keyword tutorial shows how to use declaration-site variance in Kotlin. Learn about covariance with examples.
last modified April 19, 2025
Kotlin’s variance system provides powerful tools for working with generics. The out keyword enables covariance in type parameters. This tutorial explores the out keyword in depth with practical examples.
The out keyword in Kotlin marks a type parameter as covariant. It means the generic class is a producer of the type parameter. Covariant types can be used in less specific contexts than originally declared.
A basic covariant class uses the out modifier on its type parameter. This allows assigning instances with more specific type arguments to references with less specific types.
SimpleCovariance.kt
package com.zetcode
class Box<out T>(val value: T)
fun main() { val stringBox = Box(“Hello”) val anyBox: Box<Any> = stringBox
println(anyBox.value) // Output: Hello
}
Here we declare a covariant Box class with out T. We can assign a Box<String> to a Box<Any> reference because String is a subtype of Any. The out keyword makes this assignment possible.
Kotlin’s List interface is covariant by design. It only produces elements (via get operations) and never consumes them (no add operations). This makes it safe for covariance.
ListCovariance.kt
package com.zetcode
fun printList(list: List<Any>) { list.forEach { println(it) } }
fun main() { val strings = listOf(“A”, “B”, “C”) printList(strings) // Works because List is covariant }
The printList function accepts List<Any> but we pass List<String>. This works because List in Kotlin is declared as interface List<out E>. The out modifier enables this safe upcasting.
You can create your own covariant interfaces. The out modifier ensures the type parameter is only used in output positions (return types).
CustomInterface.kt
package com.zetcode
interface Producer<out T> { fun produce(): T }
class StringProducer : Producer<String> { override fun produce() = “Hello” }
fun main() { val producer: Producer<Any> = StringProducer() println(producer.produce()) // Output: Hello }
The Producer interface is covariant in T. We can assign a StringProducer to a Producer<Any> reference. The out modifier ensures T is only used as a return type, making the assignment type-safe.
A class can have multiple type parameters with different variance annotations. Only some parameters might be covariant while others remain invariant.
MultipleParams.kt
package com.zetcode
class Response<out T, E>(val data: T, val error: E?)
fun main() { val stringResponse = Response(“Success”, null) val anyResponse: Response<Any, Nothing> = stringResponse
println(anyResponse.data) // Output: Success
}
The Response class has one covariant parameter (T) and one invariant parameter (E). We can assign Response<String, Nothing> to Response<Any, Nothing> because of the out modifier on T. The E parameter remains invariant.
Function parameters can also use covariant types. This allows more flexibility when passing arguments to functions.
FunctionParams.kt
package com.zetcode
fun processItems(items: List<out Number>) { items.forEach { println(it.toDouble()) } }
fun main() { val ints = listOf(1, 2, 3) val doubles = listOf(1.1, 2.2, 3.3)
processItems(ints)
processItems(doubles)
}
The processItems function accepts List<out Number>, meaning it can take lists of any Number subtype. We pass both List<Int> and List<Double>. The out projection makes this possible.
Arrays in Kotlin are invariant by default, but we can use array projections with out to achieve covariance when needed.
ArrayCovariance.kt
package com.zetcode
fun printArray(array: Array<out Any>) { array.forEach { println(it) } }
fun main() { val strings = arrayOf(“A”, “B”, “C”) printArray(strings) // Works due to out projection }
Although Array<T> is invariant, we can use out projection to treat it covariantly in the printArray function. This allows passing Array<String> where Array<out Any> is expected.
The out modifier can be used to enable covariant return types in class hierarchies. This allows more specific return types in subclasses.
ReturnTypes.kt
package com.zetcode
open class Animal class Dog : Animal()
interface AnimalShelter<out T : Animal> { fun adopt(): T }
class DogShelter : AnimalShelter<Dog> { override fun adopt() = Dog() }
fun main() { val shelter: AnimalShelter<Animal> = DogShelter() val animal = shelter.adopt() println(animal::class.simpleName) // Output: Dog }
The AnimalShelter interface is covariant in T. This allows DogShelter to be used as AnimalShelter<Animal>. The out modifier ensures type safety by restricting T to output positions only.
Use for producers: Apply out when the type parameter is only produced (returned) by the class. Avoid input positions: Don’t use covariant types as parameter types in methods. Prefer immutable: Covariance works best with immutable collections and data structures. Consider interfaces: Often better to make interfaces covariant rather than concrete classes. Document usage: Clearly document when and why covariance is used in your APIs.
This tutorial covered Kotlin’s out keyword in depth, showing how it enables covariance in generic types. We explored various scenarios including interfaces, classes, function parameters, and arrays. Proper use of variance annotations can make your APIs more flexible while maintaining type safety.
My name is Jan Bodnar, and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.
List all Kotlin tutorials.