skip to content

Why can't you forward a non-reified type parameter into a `reified` slot, and what patterns work around it?

level: seniorimportance: should knowfreq 35%

answer

  1. reified needs a statically known type at the call site
  2. Erased T → 'Cannot use T as reified type parameter'
  3. Push inline+reified up the chain, or
  4. Pass KClass/Class/KType as a value token
  5. Reified facade over class-based core is the library pattern

basics

~20 s

A normal type parameter is erased at runtime, so you have no real type to hand to a reified function. To bridge it, pass the type as a value — a KClass or Class — instead of relying on reified.

solid answer

~50 s

`reified` works by substituting a concrete type at an inline call site. A non-reified `T` (from an ordinary generic function or class) is erased — at the point you'd call a `reified` function, `T` is unknown, so `someReified<T>()` won't compile. You can't 'inline through' an erased parameter. Workarounds: (1) make the **outer** function `inline` with its own `reified T` and forward — `inline fun <reified T> outer() = inner<T>()` — pushing the requirement up to the caller; (2) drop `reified` entirely and accept the type as a value, e.g. `fun <T : Any> decode(json: String, klass: KClass<T>)` or `Class<T>`, then use `klass.isInstance`/`klass.cast` or reflection; (3) capture a `KType` via `typeOf<T>()` at a reified boundary and thread it downward as a value. Libraries commonly offer both a reified inline overload and a class/type-token overload for exactly this reason.

code

kotlin · 9 lines
kotlin
// Core takes a token; reified overload is just sugar.
fun <T : Any> create(klass: KClass<T>): T =
    klass.constructors.first { it.parameters.isEmpty() }.call()

inline fun <reified T : Any> create(): T = create(T::class)

class Service
val a: Service = create()              // reified path
val b: Service = create(Service::class) // token path (dynamic-friendly)

go deeper

for a junior

Recognizes that reified needs the actual type and a plain generic doesn't have it at runtime.

for a middle

Explains the erasure boundary and knows to pass a KClass/Class as a workaround.

for a senior

Confidently designs the reified-facade-over-token-core pattern and threads KType for nested generics.

for a principal

Weighs API surface (overload pairs), code-size from pervasive inlining, reflective/dynamic entry points, and binary-compatibility of inlined reified bodies.

## The core constraint `reified` is purely a compile-time, call-site mechanism: when an `inline fun <reified T>` is inlined, the compiler replaces `T` with the **concrete** type the caller used. So a `reified` parameter must be **statically known** at the call site. A non-reified type parameter — say `T` in `class Box<T>` or `fun <T> handle()` — is **erased**. At runtime there is no value or token representing it. Therefore: ```kotlin fun <T> bad(value: Any) { val ok = value is T // ERROR: erased type val r = filterList<T>() // ERROR: Cannot use T as reified type parameter. Use a class instead. } ``` The compiler error is explicit: *"Cannot use 'T' as reified type parameter. Use a class instead."* You cannot forward an erased `T` into a `reified` slot. ## Workaround 1 — push reified up the call chain Make every function in the chain `inline` + `reified`, so the concrete type flows from the real call site: ```kotlin inline fun <reified T> parse(json: String): T = parseInner<T>(json) inline fun <reified T> parseInner(json: String): T = engine.decode(typeOf<T>(), json) as T ``` This only works while the type is statically known at the outermost caller; it doesn't help truly dynamic code. ## Workaround 2 — pass the type as a value (type token) Accept the type explicitly so erasure is irrelevant: ```kotlin fun <T : Any> decode(json: String, klass: KClass<T>): T { val obj = rawDecode(json) return klass.cast(obj) } // Reified convenience overload delegates to the value-based core: inline fun <reified T : Any> decode(json: String): T = decode(json, T::class) ``` This 'reified facade over a class-based core' is the canonical library pattern: ergonomic call sites **and** a reflective entry point. ## Workaround 3 — carry a `KType` token When nested generics matter, capture `typeOf<T>()` at a reified boundary and thread the resulting `KType` (a plain value) through non-inline code: ```kotlin inline fun <reified T> decode(json: String): T = decodeByType(typeOf<T>(), json) as T fun decodeByType(type: KType, json: String): Any? { /* uses type.arguments, etc. */ } ``` ## Mental model - `reified` = compile-time substitution → needs a static type. - Non-reified `T` = erased → no static type at the boundary. - Bridge by **reifying the value** (`KClass`/`Class`/`KType`) so the type travels as data.

  • A teammate writes `inline fun <reified T> wrap() = list.filterIsInstance<T>()` and calls it from a non-inline `fun <T> caller() = wrap<T>()`. Why does it fail?
    `caller`'s `T` is non-reified and erased, so it can't be supplied to `wrap`'s reified slot. Either make `caller` inline + reified too, or refactor `wrap` to take a `KClass<T>`.
  • When is the class-token approach preferable to reified even if both compile?
    When the type is only known at runtime (plugins, config-driven dispatch, reflection), or when you want to avoid code-size blowup from inlining at many call sites, or to keep a reflective/testable entry point.

saying these in an interview costs you the question

  • Believing you can pass an erased T into a reified function
  • Not recognizing the 'Use a class instead' compiler error
  • Unaware of the KClass/Class type-token workaround
  • Thinking making just the inner function reified is enough
  • Ignoring that reified everywhere can bloat code at many call sites

context