skip to content

Inside a reified function, what is the difference between `T::class` and `typeOf<T>()`? When would you need `typeOf` instead of `T::class`?

level: middleimportance: should knowfreq 45%

answer

  1. T::class = KClass = erased class only
  2. typeOf<T>() = KType = keeps generic args + nullability
  3. List<String> vs List<Int>: only typeOf can tell them apart
  4. Serialization needs typeOf for element types
  5. KType.classifier as KClass to get the raw class back

basics

~20 s

T::class gives the class only and drops generic arguments — List<String> becomes just List. typeOf<T>() gives the full type including arguments, so it can tell List<String> from List<Int>. Use typeOf when the generic arguments matter.

solid answer

~50 s

`T::class` returns a `KClass<T>`, which represents the *class* of the type with its generic arguments erased: for `T = List<String>` you get `KClass` for `List`, with no element type. `typeOf<T>()` (from `kotlin.reflect`) returns a `KType`, a full reflective type that preserves generic arguments, nullability, and variance — so `typeOf<List<String>>()` differs from `typeOf<List<Int>>()`. You reach for `typeOf<T>()` when the type arguments matter: e.g. serialization libraries (kotlinx.serialization's `serializer()`, Jackson/Gson reflective helpers, Retrofit-style response parsing) that must know the element type to deserialize a `List<User>` correctly. `T::class` is enough when you only need the raw class, e.g. `T::class.java` for a JVM `Class`, `when` over sealed subtypes, or simple `is`/`as` checks. Note `typeOf` itself does not require reified — `inline fun <reified T> typeInfo() = typeOf<T>()` is the common wrapper because reified provides the concrete `T` at the call site.

code

kotlin · 9 lines
kotlin
import kotlin.reflect.typeOf

inline fun <reified T> describe(): String {
    val cls = T::class.simpleName            // "List"
    val full = typeOf<T>().toString()        // "kotlin.collections.List<kotlin.String>"
    return "$cls | $full"
}

// describe<List<String>>() -> "List | kotlin.collections.List<kotlin.String>"

go deeper

for a junior

Can state that T::class gives the class and typeOf<T>() gives the full type, even if fuzzy on details.

for a middle

Clearly contrasts KClass (erased) vs KType (keeps generic args/nullability) and gives the List<String> vs List<Int> example.

for a senior

Connects typeOf<T>() to real serialization APIs and explains recovering KClass from KType.classifier.

for a principal

Weighs runtime/reflection cost of typeOf, designs generic-aware keys/serializers, and knows the kotlin-reflect dependency implications.

## Two reflective views of a type When you have a reified `T`, Kotlin gives you two different reflective handles: ### `T::class` -> `KClass<T>` A `KClass` represents a **class** (the `Class` object on the JVM, roughly). It is the *erased* view: generic arguments are gone. ```kotlin inline fun <reified T> klass() = T::class klass<List<String>>() // KClass for List — the <String> is NOT here klass<List<Int>>() // same KClass for List ``` From a `KClass` you get `.java` (the JVM `Class<T>`), `.simpleName`, `.qualifiedName`, `.sealedSubclasses`, etc. Good for `is`/`as`, JVM interop, and class-level reflection. ### `typeOf<T>()` -> `KType` `typeOf<T>()` (in `kotlin.reflect`) returns a **`KType`**: a fully resolved type that keeps: - generic **type arguments** (`KType.arguments`), - **nullability** (`KType.isMarkedNullable`), - **variance** of each argument. ```kotlin import kotlin.reflect.typeOf inline fun <reified T> ktype() = typeOf<T>() ktype<List<String>>() // kotlin.collections.List<kotlin.String> ktype<List<Int>>() // kotlin.collections.List<kotlin.Int> -> distinct! ktype<String?>() // isMarkedNullable == true ``` You can recover the raw class from a `KType` via `(type.classifier as? KClass<*>)`. ## When you need `typeOf` over `T::class` - **Serialization / deserialization**: turning JSON into `List<User>` requires knowing the element type. kotlinx.serialization's `serializer(typeOf<T>())` and reflective JSON libs depend on this. - **Generic-aware caching / DI keys**: distinguishing `Box<Int>` from `Box<String>`. - **Anything that must inspect generic arguments at runtime**. Use plain `T::class` when only the class matters (JVM `Class`, sealed-subclass dispatch, simple instance checks). `typeOf` is more expensive and pulls in `kotlin-reflect`-style metadata for nested generics. ## Note on reified `typeOf<T>()` is what makes erasure-aware serialization ergonomic, and it is almost always paired with `reified` so the call site supplies the concrete `T`.

  • How do you get the raw `KClass` back out of a `KType`?
    Use its `classifier`: `(typeOf<T>().classifier as? KClass<*>)`. The classifier is the class/interface (or a type parameter); cast it to `KClass` when it is a concrete class.
  • Does `typeOf<T>()` require `reified`?
    `typeOf` itself takes the type as a generic argument, but to call it with a *call-site* type you wrap it in `inline fun <reified T>`. Without reified you could only call `typeOf<SomeConcreteType>()` with a literal type.

saying these in an interview costs you the question

  • Saying T::class preserves generic type arguments
  • Claiming typeOf<T>() and T::class are interchangeable
  • Not knowing typeOf lives in kotlin.reflect / returns KType
  • Thinking you can deserialize List<User> using only T::class
  • Confusing KType.isMarkedNullable with the class being nullable

context