How does Kotlin represent generic types at runtime via KType, and how do typeOf<T>() and reified parameters overcome JVM type erasure?
answer
- JVM erases generics; List<String> -> List at runtime
- KType = classifier + arguments(variance) + nullability
- typeOf<T>() captures full static type
- reified needs inline; enables T::class, is T, as T
- Powers decodeFromString<T> / fromJson<T>
basics
~10 sThe JVM erases generics, so List<String> looks like List at runtime. Kotlin's typeOf<T>() captures the full type, and reified type parameters in inline functions keep T available so you can inspect or use it.
solid answer
~40 sOn the JVM, generic type arguments are **erased**: `List<String>` and `List<Int>` share one runtime class. Kotlin recovers full type info with **`KType`**, which carries the `classifier` (a `KClassifier`, usually a `KClass`), the list of `arguments` (`KTypeProjection`, including variance), and `isMarkedNullable`. You obtain a `KType` from `typeOf<T>()` (a stdlib intrinsic) or from members like `KCallable.returnType` / `KProperty.returnType`. **`reified`** type parameters work only on `inline fun`: the compiler substitutes the actual type at each call site, so inside the function you can write `T::class`, `is T`, `as T`, or `typeOf<T>()`. This is why `inline fun <reified T> Gson.fromJson(...)` and `Json.decodeFromString<T>()` can resolve the target type. Without inline+reified you cannot do `is T` or `T::class`. `serializer(typeOf<T>())` bridges a runtime KType to a serializer.
code
kotlin · 13 linesimport kotlin.reflect.typeOf
import kotlin.reflect.KType
inline fun <reified T> typeInfo(): KType = typeOf<T>()
fun main() {
val k = typeInfo<Map<String, List<Int?>>>()
println(k.isMarkedNullable) // false
val firstArg = k.arguments.first().type // String
println(firstArg)
val nested = k.arguments[1].type // List<Int?>
println(nested?.arguments?.first()?.type?.isMarkedNullable) // true (Int?)
}go deeper
Knows the JVM erases generics and reified lets you use T inside inline functions.
Explains typeOf<T>() and the inline+reified requirement with is T / T::class.
Describes KType structure (classifier, arguments, variance, nullability) and how it powers serialization/JSON libraries.
Reasons about codegen/bytecode-size tradeoffs of reified inlining and designs APIs that bridge erased generics to KType-driven runtime behavior.
## JVM type erasure The JVM implements generics by **erasure**: type arguments are removed after compilation. At runtime `List<String>` is just `List`, and `x is List<String>` is impossible to check fully — only `x is List<*>` is allowed. ## KType — Kotlin's runtime type representation `KType` represents a *use* of a type with its arguments and nullability: - `classifier: KClassifier?` — the underlying `KClass` (or type parameter) - `arguments: List<KTypeProjection>` — the type arguments, each with variance (`in`/`out`/invariant) and a nested `KType` - `isMarkedNullable: Boolean` You get a `KType` from: - `typeOf<T>()` — a stdlib **intrinsic** the compiler fills in with the full static type - reflective members: `kProperty.returnType`, `kFunction.returnType`, `kParameter.type` ```kotlin import kotlin.reflect.typeOf val t = typeOf<Map<String, List<Int?>>>() println(t.classifier) // class kotlin.collections.Map println(t.arguments) // [String, List<Int?>] ``` ## reified type parameters A normal type parameter `T` is erased inside a function. An **`inline fun`** can mark a parameter `reified`, and the compiler **substitutes the concrete type** at every call site, so inside the body you may: - call `T::class` - use `x is T` / `x as T` - call `typeOf<T>()` ```kotlin inline fun <reified T> Any.cast(): T? = this as? T inline fun <reified T : Enum<T>> parseEnum(name: String): T = enumValues<T>().first { it.name == name } ``` This is exactly the mechanism behind `Json.decodeFromString<User>(s)` and `gson.fromJson<T>(...)`: the reified `T` (often turned into `typeOf<T>()`) supplies the otherwise-erased target type. ## Bridging to serialization ```kotlin import kotlinx.serialization.serializer val ser = serializer(typeOf<List<User>>()) // KType -> KSerializer ``` ## Limitations - `reified` requires `inline`; you cannot store a reified `T` in a class field or use it from a non-inline function. - Reified inlining can increase bytecode size at each call site. - `typeOf` captures the *static* type at the call site, not a runtime value's type.
- Why can't you write a non-inline fun <T> isInstance(x: Any) = x is T?T is erased in a regular function, so the runtime has no type to check against; you need inline + reified so the compiler substitutes the concrete type.
- What's the difference between T::class and typeOf<T>() inside a reified function?T::class gives only the raw KClass (no type arguments), while typeOf<T>() returns a full KType including generic arguments and nullability.
Erasure throws away the shipping label; typeOf<T>() and reified are like photographing the label before it's removed so you still know what's inside.
saying these in an interview costs you the question
- Claiming you can do x is List<String> at runtime
- Using reified without inline
- Thinking T::class preserves generic arguments
- Saying typeOf inspects a value's runtime type
- Not connecting reified to decodeFromString<T>