skip to content

When designing a library API, how do you decide between a `reified` inline overload and a `KClass`/type-token parameter, and what are the long-term tradeoffs?

level: principalimportance: nice to knowfreq 22%

answer

  1. reified = ergonomic + compile-time, but inlined (code size, no reflection)
  2. token (KClass/KType) = dynamic, reflective, Java-friendly
  3. Pattern: thin reified facade over non-inline core
  4. Nested generics/nullability → typeOf/KType, not KClass
  5. Public inline bodies are a binary-compatibility hazard

basics

~20 s

Use reified for clean call sites when the type is known at compile time. Use a KClass/Class parameter when the type is dynamic or you want a reflective entry point. Big libraries usually offer both, with reified delegating to the token version.

solid answer

~40 s

`reified` overloads give ergonomic call sites (`decode<User>(json)`) and compile-time safety, but they **inline** the body — every call site duplicates code, and the reified function can't be invoked reflectively or from non-inline/dynamic code. A `KClass<T>`/`Class<T>`/`KType` parameter keeps a single non-inlined implementation, works for runtime-determined types, and is reflection- and Java-interop-friendly — at the cost of a clunkier call site and weaker compile-time guarantees. The mature pattern: a non-inline core that takes the type as a value, plus a thin `inline fun <reified T>` facade delegating to it (`= core(T::class)` or `core(typeOf<T>())`). This gives ergonomics without losing dynamic access. Watch binary compatibility: inlined reified bodies are copied into consumers, so changing a public inline function's body is a binary-compatibility hazard. Also weigh code-size from many call sites and whether nested generics force `typeOf` over `KClass`.

code

kotlin · 9 lines
kotlin
// Core: testable, reflection- and Java-friendly, single implementation.
fun <T : Any> Registry.resolve(type: KType): T { /* ... */ }

// Facade: concise call sites, delegates, stays tiny for binary safety.
inline fun <reified T : Any> Registry.resolve(): T = resolve(typeOf<T>())

// Dynamic caller still works without reified:
val t: KType = computeTypeAtRuntime()
val anything = registry.resolve<Any>(t)

go deeper

for a junior

Can state reified is for compile-time-known types and is more convenient at the call site.

for a middle

Knows to offer a class-parameter alternative for dynamic types and that reified inlines code.

for a senior

Designs the facade-over-core pattern and picks typeOf vs KClass based on nested generics.

for a principal

Balances ergonomics, code size, reflection/Java interop, binary compatibility of inline bodies, and testability across the public API surface.

## The decision axes ### 1. Is the type known at compile time? - **Yes** → a `reified` inline overload shines: `decode<User>(json)` is concise and type-checked. - **No / runtime-determined** (plugin systems, config-driven dispatch, frameworks) → you need a **value** carrying the type: `KClass<T>`, `Class<T>`, or `KType`. ### 2. Nested generics / nullability? - If you must reconstruct `List<User>` or honor `String?`, `KClass` is insufficient (erased element type). Use `typeOf<T>()` → `KType`, captured at a reified boundary and threaded as a value. ### 3. Reflection / Java interop? - A `reified` function is inlined and **cannot be called via reflection** or easily from Java. A token-based core is reflection-friendly and Java-callable. ## The canonical pattern: facade over core ```kotlin // Single real implementation, type passed as data: fun <T : Any> decode(type: KType, json: String): T { /* ... */ } // Ergonomic, compile-time-safe facade: inline fun <reified T : Any> decode(json: String): T = decode(typeOf<T>(), json) ``` This is what `kotlinx.serialization`, DI containers, and many JSON libraries do: a reified entry point for app code, a token-based one for dynamic code. ## Long-term tradeoffs ### Code size Inlining copies the body into **every** call site. A heavily-used reified helper with a large body inflates consumer bytecode. Keep reified facades **thin** — delegate immediately to a non-inline core. ### Binary compatibility Public `inline` functions are compiled **into** their callers. If you change the body of a published inline reified function, already-compiled consumers keep the old body until recompiled — a subtle binary-compatibility and patching hazard. The thin-facade rule also limits blast radius here. Avoid referencing non-public declarations from public inline bodies. ### Evolvability and testing A token-based core is easier to test (pass any `KClass`/`KType`), mock, and extend without touching call sites. The reified overload is sugar you can change freely if it just delegates. ### Performance nuance Inlining removes a call and lets the type check be monomorphic at each site — occasionally a real micro-win — but rarely the deciding factor versus the maintainability concerns above. ## Checklist - Compile-time type + ergonomics → provide a reified facade. - Dynamic type, reflection, Java callers, nested generics → token core (`KClass`/`KType`). - Ship **both**, with reified delegating to the core. - Keep public inline bodies tiny and free of non-public references.

  • Why is changing the body of a published public inline reified function risky?
    Inline bodies are copied into consumers at their compile time. Existing binaries keep the old body until recompiled, so a change can leave callers on stale logic — a binary-compatibility hazard. Keeping facades thin and delegating to a stable core mitigates it.
  • Your reified helper is huge and called in hundreds of places. What's the concern and fix?
    Code-size blowup from duplicating the body at every call site. Refactor so the inline reified function is a thin delegate to a single non-inline core; only the tiny delegation is inlined.

saying these in an interview costs you the question

  • Recommending reified everywhere regardless of dynamic needs
  • Ignoring code-size cost of inlining large reified bodies
  • Unaware reified functions can't be called reflectively/from Java
  • Not knowing public inline bodies are a binary-compatibility concern
  • Using KClass where nested generics demand KType/typeOf

context