In Kotlin, contravariance helps you use a more general type where a more specific type is expected. It makes your code flexible and safe when working with types that consume data.
In variance (contravariance) in Kotlin
interface Consumer<in T> { fun consume(item: T) }
The in keyword before the generic type T marks it as contravariant.
Contravariant types can only be used as input (parameters), not as output (return types).
T. The in keyword means it can accept T or any supertype of T.interface Consumer<in T> { fun consume(item: T) }
Int. Thanks to contravariance, you can pass a Consumer<Number> safely.fun feedConsumer(consumer: Consumer<Int>) { consumer.consume(42) consumer.consume(100) }
Consumer<Number> can be assigned to a Consumer<Int> variable.val numberConsumer: Consumer<Number> = object : Consumer<Number> { override fun consume(item: Number) { println("Consumed: $item") } } val intConsumer: Consumer<Int> = numberConsumer
This program shows contravariance by assigning a Consumer to a Consumer variable. The feedConsumer function calls consume with different Int types.
interface Consumer<in T> { fun consume(item: T) } class NumberConsumer : Consumer<Number> { override fun consume(item: Number) { println("Consumed Number: $item") } } fun feedConsumer(consumer: Consumer<Int>) { consumer.consume(10) consumer.consume(20) } fun main() { val numberConsumer = NumberConsumer() // Contravariance allows this assignment val intConsumer: Consumer<Int> = numberConsumer feedConsumer(intConsumer) }
Contravariant types cannot be returned from functions because they are only safe as inputs.
Use in only when the generic type is used as a function parameter, never as a return type.
Contravariance is the opposite of covariance, which uses out and allows returning types.
Use in to mark a generic type as contravariant, meaning it can accept supertypes safely.
Contravariance is useful for consumers that only take input and never produce output.
It helps make your Kotlin code more flexible and type-safe when working with generic types.
Practice
in keyword mean when used in Kotlin generics?Solution
Step 1: Understand variance keywords in Kotlin generics
Kotlin usesinfor contravariance andoutfor covariance.Step 2: Identify the meaning of
ininmeans the generic type can accept supertypes safely as input but cannot produce output of that type.Final Answer:
It marks a generic type as contravariant, allowing it to accept supertypes safely. -> Option AQuick Check:
in= contravariant = accept supertypes [OK]
- Confusing 'in' with 'out' variance
- Thinking 'in' allows output of the generic type
- Assuming 'in' restricts to exact types only
Solution
Step 1: Recall syntax for contravariant generic declaration
Contravariance usesinbefore the generic type parameter in the interface declaration.Step 2: Check method parameter usage
Method parameter can use the generic type normally; the variance is declared at the interface level.Final Answer:
interface Consumer { fun consume(item: T) } -> Option BQuick Check:
Contravariant syntax =inbefore T [OK]
- Using 'out' instead of 'in' for contravariance
- Placing 'in' inside method parameters incorrectly
- Omitting variance keyword entirely
interface Consumer {
fun consume(item: T)
}
fun feed(consumer: Consumer<Number>) {
consumer.consume(42)
}
val intConsumer: Consumer<Int> = object : Consumer<Int> {
override fun consume(item: Int) {
println("Consumed: $item")
}
}
feed(intConsumer)What will happen when this code runs?
Solution
Step 1: Understand contravariance direction
Contravariance means Consumer<Supertype> can accept Consumer<Subtype>, not the other way around.Step 2: Analyze the call feed(intConsumer)
feed expects Consumer<Number>, but intConsumer is Consumer<Int>. Since Int is subtype of Number, Consumer<Int> is not subtype of Consumer<Number> when contravariant.Final Answer:
Compilation error because Consumer<Int> is not a subtype of Consumer<Number>. -> Option CQuick Check:
Contravariance reverses subtype relation [OK]
- Assuming Consumer<Int> can be passed where Consumer<Number> is expected
- Confusing covariance and contravariance
- Expecting runtime error instead of compile-time error
interface Consumer {
fun consume(item: T)
}
fun process(consumer: Consumer<Number>) {
consumer.consume(3.14)
}
val doubleConsumer: Consumer<Double> = object : Consumer<Double> {
override fun consume(item: Double) {
println("Consumed double: $item")
}
}
process(doubleConsumer)Why does this code cause a compilation error?
Solution
Step 1: Recall contravariance subtype rules
ForConsumer, Consumer<Supertype> can accept Consumer<Subtype>, not the reverse.Step 2: Check the types in the call
process expects Consumer<Number>, but doubleConsumer is Consumer<Double>. Double is subtype of Number, so Consumer<Double> is not subtype of Consumer<Number>.Final Answer:
Because Consumer<Double> cannot be passed where Consumer<Number> is expected due to contravariance. -> Option AQuick Check:
Contravariance reverses subtype relation [OK]
- Thinking Double is not subtype of Number
- Confusing method parameter types
- Assuming runtime error instead of compile-time
Number or any of its supertypes safely. Which declaration correctly uses contravariance to allow this flexibility?interface Consumer {
fun consume(item: T)
}
fun acceptConsumer(consumer: Consumer<___>) {
consumer.consume(100)
}Solution
Step 1: Understand contravariance usage
Contravariance allows passing a consumer of a supertype to a function expecting a consumer of a subtype.Step 2: Determine the correct generic type
To accept consumers that can consume Number or any supertype safely (i.e., Consumer<Number> and Consumer<Any>), useAnyas the generic parameter.Final Answer:
Any -> Option DQuick Check:
Contravariant consumer accepts supertypes [OK]
- Using supertype like Any, which only accepts Consumer<Any>
- Using subtype like Int
- Using Nothing which is subtype of all types
Ready to prove it?
Pass the test and this pattern moves unknown
