Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Covariance in Kotlin is a type-system feature that preserves the subtyping relationship of generic type parameters. If type A is a subtype of type B, a covariant generic type Generic<A> is considered a subtype of Generic<B>. Kotlin implements covariance through two mechanisms: declaration-site variance using the out modifier, and use-site variance through type projections.

Declaration-Site Variance

By marking a type parameter with out at the class or interface declaration, you instruct the compiler to restrict the usage of that type parameter strictly to out positions (return types and read-only val properties). The type can only be produced by the generic component, never consumed as an argument.
interface Producer<out T> {
    val defaultItem: T // Valid: T is in an 'out' position (read-only property)
    
    fun next(): T // Valid: T is in an 'out' position (return type)
    
    // fun add(item: T) // Compilation Error: T cannot occur in an 'in' position
}
Without covariance (invariance), generic types are strictly typed regardless of their type arguments. Covariance safely relaxes this strictness, allowing upcasting of the generic type.
open class Base
class Derived : Base()

// A covariant class
class Box<out T>(val value: T)

fun demonstrateCovariance() {
    val derivedBox: Box<Derived> = Box(Derived())
    
    // Valid assignment due to covariance: Box<Derived> is a subtype of Box<Base>
    val baseBox: Box<Base> = derivedBox 
}

Type Safety and the out Restriction

The compiler enforces the “produce-only” rule to prevent heap pollution and runtime ClassCastExceptions. If a covariant type parameter were allowed in an in position (such as a function parameter or a mutable property), the type system could be compromised. Using var with an out type parameter in a primary constructor is immediately illegal. The var keyword generates a setter, which places the type parameter in an in position, causing a compilation error on the property declaration itself.
open class Animal
class Dog : Animal()
class Cat : Animal()

// Compilation Error: Type parameter T is declared as 'out' but occurs in 'in' position
// class InvalidBox<out T>(var item: T) {
//     fun setItem(newItem: T) { ... } // Also Illegal: T in 'in' position
// }

// Hypothetical scenario demonstrating why the compiler forbids this:
class MutableBox<T>(var item: T) // Invariant class

fun demonstrateRestriction() {
    val dogBox: MutableBox<Dog> = MutableBox(Dog())
    
    // If MutableBox were covariant, the following would compile:
    // val animalBox: MutableBox<Animal> = dogBox 
    
    // Which would allow inserting a Cat into a Box<Dog>, breaking type safety:
    // animalBox.item = Cat() 
}
Because Kotlin restricts out parameters to read-only positions, a covariant Box<Animal> can only ever return an Animal (which is safely guaranteed to be a Dog in the underlying instance), and the compiler prevents you from writing a Cat into it.

Use-Site Variance (Type Projections)

Certain types, such as Array or MutableList, must remain invariant at the declaration site because they both produce and consume elements. To achieve covariance for these types for a specific operation, Kotlin uses use-site variance, also known as type projections. By applying the out modifier at the point of usage, you project the invariant type into a covariant one. This restricts the projected instance to its “produce” methods, effectively disabling any methods that consume the type parameter.
// Array is invariant at the declaration site: class Array<T>

fun copyElements(source: Array<out Animal>, destination: Array<Animal>) {
    // 'source' is a covariant type projection. 
    // We can safely read 'Animal' from it.
    for (i in source.indices) {
        destination[i] = source[i]
    }
    
    // Compilation Error: Cannot write to an 'out' projected array.
    // The setter expects 'Nothing' because the exact subtype is unknown.
    // source[0] = Dog() 
}

fun demonstrateUseSiteVariance() {
    val dogs: Array<Dog> = arrayOf(Dog(), Dog())
    val animals: Array<Animal> = arrayOf(Animal(), Animal())
    
    // Valid: Array<Dog> is passed where Array<out Animal> is expected
    copyElements(dogs, animals) 
}

@UnsafeVariance

In specific architectural scenarios, a type parameter must be covariant at the declaration site, but technically needs to appear in an in position where the internal implementation guarantees type safety (e.g., checking equality). Kotlin provides the @UnsafeVariance annotation to suppress the compiler’s variance conflict errors.
interface Collection<out E> {
    // E appears in an 'in' position, but is safe because 'contains' 
    // does not mutate the collection or cast the element unsafely.
    fun contains(element: @UnsafeVariance E): Boolean
}
Master Kotlin with Deep Grasping Methodology!Learn More