Generics
Type erasure, reified type parameters, and variance in Kotlin generics.
7 questions
JuniorTheoryVery commonWhat do out and in mean on a type parameter in Kotlin?
What do out and in mean on a type parameter in Kotlin?
They declare variance once, on the class itself. out T restricts T to producer positions — you may return a T, never accept one — and makes Box<Dog> a Box<Animal>. in T restricts T to consumer positions and reverses that. Without either, the type is invariant.
Common mistakes
- ✗Swapping the two — expecting an
out Tclass to still accept aTas a function parameter - ✗Assuming a generic class is covariant by default, the way a Java array is
- ✗Thinking variance is enforced at runtime rather than by the compiler at the declaration
Follow-up questions
- →Why is
MutableList<T>invariant whileList<T>is declaredout T? - →What do you write at a single use site when the class itself cannot be declared variant?
JuniorTheoryCommonWhat is type erasure, and why can't you write T::class.java for a plain generic T?
What is type erasure, and why can't you write T::class.java for a plain generic T?
On the JVM a generic type argument is erased at compile time — at runtime List<String> is just List. So a plain type parameter T has no runtime class token; T::class.java won't compile because the actual type is unknown at runtime.
Common mistakes
- ✗Assuming generic type arguments survive to runtime as they do in C++ templates
- ✗Thinking
T::classworks inside any generic function - ✗Confusing erasure with type inference, which happens at compile time too
Follow-up questions
- →Which Kotlin feature lets a function recover its type argument at runtime?
- →Why can two overloads differing only in generic type argument clash after erasure?
MiddleDebuggingCommonHow do you fix filterWrapper so it filters by T without reflection?
How do you fix filterWrapper so it filters by T without reflection?
A plain T is erased, so T::class.java is unavailable. Make it inline with a reified parameter: inline fun <reified T> filterWrapper(data: List<Any>) = data.filterIsInstance<T>(). Inlining substitutes T's real type at the call site — no reflection.
Common mistakes
- ✗Trying
T::class.javain a non-inline function whereTis erased - ✗Thinking only passing a
Class<T>argument can solve it, missingreified - ✗Marking the parameter
reifiedwithout also making the functioninline
Follow-up questions
- →Why must a
reifiedtype parameter always live on aninlinefunction? - →What runtime check does
filterIsInstance<T>()perform on each element?
JuniorTheoryOccasionalWhat does a typealias introduce in Kotlin, and what does it not introduce?
What does a typealias introduce in Kotlin, and what does it not introduce?
It introduces an alternative name for an existing type, and nothing else. The compiler expands the alias back to the original type, so no new type, no new class and no wrapper object appear. typealias UserId = String and String are interchangeable everywhere.
Common mistakes
- ✗Expecting the compiler to reject a plain
Stringwheretypealias UserId = Stringis declared - ✗Thinking a
typealiascreates a class or a wrapper and therefore costs something at runtime - ✗Confusing it with a
value class, which does introduce a distinct type the compiler enforces
Follow-up questions
- →What is a
typealiasgenuinely good for if it adds no type safety at all? - →Can a
typealiasbe declared inside a class or a function body, and why is that so?
MiddleTheoryOccasionalHow does Kotlin's declaration-site out/in differ from Java's ? extends/? super?
How does Kotlin's declaration-site out/in differ from Java's ? extends/? super?
Kotlin states variance once, on the class, and every use site is variant for free. Java has no declaration-site variance: each use site must repeat ? extends T or ? super T. Both are compile-time only — erasure leaves the same raw type — so Kotlin emits Java wildcards on the interop boundary.
Common mistakes
- ✗Thinking Kotlin has no use-site projections at all, only the declaration-site form
- ✗Believing variance survives erasure and is enforced by the JVM at runtime
- ✗Expecting a Kotlin
out Tclass to appear without wildcards in Java-facing signatures
Follow-up questions
- →When does the compiler not emit a wildcard, and what does
@JvmSuppressWildcardschange? - →Why does Java's producer-extends-consumer-super rule
PECSbecome unnecessary for anout Tclass?
MiddleTheoryOccasionalWhen does a typealias fail to give the type safety that a value class would?
When does a typealias fail to give the type safety that a value class would?
Always — it is a name, not a type. typealias UserId = String still accepts any String, and a UserId mixes freely with an OrderId because both expand to String; assignability and variance stay those of the aliased type. A value class UserId(val v: String) is a distinct type the compiler enforces.
Common mistakes
- ✗Expecting the compiler to reject an
OrderIdwhere aUserIdis declared - ✗Thinking a
typealiascan narrow or change the variance of the type it aliases - ✗Believing a
value classalways allocates, and preferring an alias to avoid that cost
Follow-up questions
- →When is a
value classforced to box, losing its wrapper-free representation? - →What is a
typealiasstill worth using for, if it adds no safety of its own?
MiddleTheoryRareWhat does the star projection List<*> allow you to do, and what does it forbid?
What does the star projection List<*> allow you to do, and what does it forbid?
List<*> says the element type is some single unknown type. Reading is allowed: every element comes back as Any?, the upper bound. Writing is rejected, because the compiler cannot prove your value matches that unknown type — so MutableList<*>.add(x) does not compile.
Common mistakes
- ✗Reading
List<*>asList<Any?>and expecting to be able to add an element to it - ✗Thinking
*is Kotlin's raw type, allowing unchecked writes behind a warning - ✗Expecting a read to give back the concrete element type rather than the upper bound
Follow-up questions
- →How does
MutableList<*>differ fromMutableList<Any?>at the call site? - →What does
*project to for a parameter declaredin Trather thanout T?