Inside a `reified` function, what is the difference between `T::class` and `typeOf<T>()`, and when do you need each?
answer
- T::class = class only, no generics, no nullability
- typeOf<T>() = full KType: arguments + nullability
- List<String> vs List<Int>: same KClass, different KType
- Serialization needs typeOf for nested generics
- javaType / full reflection needs kotlin-reflect
basics
~10 sT::class gives you the class object only. typeOf<T>() gives you the full type — including nullability and inner generic types like List<String>. Use typeOf when nested generics or nullability matter.
solid answer
~40 s`T::class` returns a `KClass<T>` (or `T::class.java` a `Class<T>`): just the raw class, with no generic arguments and no nullability — `List<String>` and `List<Int>` both yield `List::class`. `typeOf<T>()` returns a `KType`, the full reflected type: it carries type arguments (`KType.arguments`), nullability (`KType.isMarkedNullable`), and the classifier. So `typeOf<List<String>>()` is distinct from `typeOf<List<Int>>()`. You typically need `typeOf<T>()` in serialization/deserialization, where you must reconstruct `List<User>` not just `List`. `T::class` is enough for simple `is`/`as` checks, logging the class name, or instantiating via reflection. Both rely on `reified` to capture T; `typeOf<T>()` itself is a `reified` stdlib function. Note `typeOf` pulls in `kotlin-reflect` semantics for full fidelity.
code
kotlin · 10 linesimport kotlin.reflect.typeOf
inline fun <reified T> describe(): String {
val cls = T::class.simpleName
val kt = typeOf<T>()
return "class=$cls type=$kt nullable=${kt.isMarkedNullable}"
}
describe<List<String>>() // class=List type=...List<String> nullable=false
describe<List<Int>?>() // class=List type=...List<Int>? nullable=truego deeper
Knows T::class gives the class and can name simpleName/java; may not know typeOf.
Clearly contrasts KClass vs KType, knows typeOf preserves generics and nullability, and picks the right one for serialization.
Explains the kotlin-reflect dependency, javaType interop, and that typeOf captures static call-site type, not runtime value type.
Designs APIs choosing typeOf-based overloads, weighs reflect classpath cost, and reasons about generic-preserving deserialization contracts.
## Two levels of runtime type information When a `reified T` lets you inspect the type at runtime, Kotlin offers two granularities. ### `T::class` — the class only - Expression: `T::class` → `KClass<T>`; `T::class.java` → `Class<T>`. - A **class** carries no generic arguments. Because of JVM erasure, `T::class` for `List<String>` and `List<Int>` are **identical** (`List::class`). It also carries no nullability information. - Cheap and reflection-light. Good for: type checks, getting `simpleName`/`qualifiedName`, constructing instances, switching on a class. ```kotlin inline fun <reified T : Any> tag(): String = T::class.simpleName ?: "?" tag<List<String>>() // "List" tag<List<Int>>() // "List" — same! ``` ### `typeOf<T>()` — the full `KType` - `typeOf<T>()` is a stdlib `inline fun <reified T> typeOf(): KType`. - A `KType` describes a **fully reified type usage**: a `classifier` (the `KClass`), a list of `arguments` (the generic type projections), and `isMarkedNullable`. - It distinguishes `List<String>` from `List<Int>` and `String` from `String?`. ```kotlin import kotlin.reflect.typeOf typeOf<List<String>>() // kotlin.collections.List<kotlin.String> typeOf<List<Int>>() // kotlin.collections.List<kotlin.Int> typeOf<String?>().isMarkedNullable // true ``` ## When to use which | Need | Use | |------|-----| | Simple `is`/`as`, class name, instantiate | `T::class` / `T::class.java` | | Reconstruct nested generics (`Map<String, List<User>>`) | `typeOf<T>()` | | Distinguish nullable vs non-null | `typeOf<T>()` | | Pass a `java.lang.reflect.Type` to a Java lib | `typeOf<T>().javaType` (reflect) | ## Why serialization libraries care Deserializing JSON into `List<User>` needs the element type. With only `T::class` you'd get `List` and lose `User`. So modern APIs do: ```kotlin inline fun <reified T> decode(json: String): T = decodeFromType(typeOf<T>(), json) ``` This is exactly why `kotlinx.serialization` exposes `inline fun <reified T> decodeFromString(...)`. ## Caveats - Full `KType` inspection (arguments, `javaType`) generally requires `kotlin-reflect` on the classpath. - `typeOf` captures the **static** type at the call site, not the runtime class of any value.
- Can `typeOf<T>()` recover the element type of a `List<T>` when `T` itself is a non-reified parameter?No. `typeOf` captures the static type at the call site. If `T` is an unreified type parameter from an outer scope, you can't pass it into a reified slot, so the information isn't available.
- Does `T::class` require kotlin-reflect?Getting the `KClass` and `simpleName` works with the lightweight built-in reflection. Deep `KType`/`javaType` inspection needs the full kotlin-reflect dependency.
saying these in an interview costs you the question
- Claiming T::class preserves generic arguments like List<String>
- Saying typeOf<T>() and T::class are interchangeable
- Not knowing typeOf carries nullability
- Thinking typeOf reads a value's runtime class instead of the static type
- Unaware serialization uses typeOf to keep nested generics