Explain Kotlin's declaration-site variance with in and out

Understanding declaration-site variance versus wildcards.
out T means covariant producer (read), in T means contravariant consumer (write), removing wildcard noise.
Confusing in/out with bounds or claiming immutability.
WHAT THIS TESTS: This question probes whether you understand variance at the type-system level and can contrast Kotlin's declaration-site variance with Java's use-site wildcards. The interviewer wants to see that you know why API designers choose in or out, not just that you have memorized the keywords. They are looking for evidence that you can reason about producer and consumer roles for generic types and avoid common misconceptions about immutability.
A GOOD ANSWER COVERS: First, define out T as covariance. When a generic class only returns T and never accepts T as a parameter, marking it with out lets the compiler treat Source<String> as a subtype of Source<Object>. Second, define in T as contravariance. When a class only consumes T, such as a comparator or callback, marking it with in lets the compiler treat Consumer<Object> as a subtype of Consumer<String>. Third, explain the practical benefit: the API author makes the variance decision once at the declaration site, so callers never need to write ? extends or ? super. Fourth, relate this to PECS: out corresponds to Producer-Extends and in corresponds to Consumer-Super, but Kotlin bakes this into the type declaration rather than forcing it at every usage point.
COMMON WRONG ANSWERS: A red flag is claiming that out or in makes a class immutable. Variance guarantees type safety about where T appears, not immutability; a covariant list might still be mutable via clear() or swap operations that do not mention T. Another red flag is confusing in and out with upper or lower bounds on the type parameter itself; they are about usage position, not inheritance constraints. A third red flag is describing covariance and contravariance purely in abstract math terms without connecting them to read versus write operations on a generic API.
LIKELY FOLLOW-UPS: The interviewer may ask where type projections fit in, specifically use-site variance with the star projection or explicit in and out at the call site. They might ask why MutableList cannot be covariant, which lets you demonstrate that write operations break subtyping. They could also ask how declaration-site variance interacts with Java interop, since Java sees Kotlin's out as ? extends and in as ? super at the bytecode level.
ONE CONCRETE EXAMPLE: Consider an interface Source<out T> with a single method fun nextT(): T. Because T only appears in an out position, you can safely assign a Source<String> to a variable of type Source<Any>. If Source were invariant, the compiler would reject this. Conversely, consider an interface Sink<in T> with a method fun accept(value: T). Because T only appears in an in position, a Sink<Any> can be used wherever a Sink<String> is expected. This mirrors Java's PECS rule but removes the need for wildcards at every call site.
Source: kotlinlang.org
Read the original → kotlinlang.org
- #kotlin
- #generics
- #variance
- #type-system
- #api-design
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.