skip to content

The inline + reified Pattern

reified requires inline because the trick is substitution: the body is copied to the call site with T replaced by a concrete class. Being able to explain that mechanism, not just use it, is the difference in an interview answer.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

Why must a function be marked `inline` for its type parameter to be `reified`? What does the compiler do?

level: juniorimportance: must knowfreq 70%

answer

  1. inline = body copied to call site
  2. call site knows the real type
  3. reified only allowed with inline
  4. erasure removes T at runtime
  5. is T / T::class become legal

basics

~20 s

Because inline copies the function's body into each call site. There the compiler already knows the real type you used, so it can replace the type parameter with that concrete class. A normal function has no copy and no known type.

solid answer

~40 s

On the JVM, generic type arguments are erased at runtime — a normal generic function never knows its concrete `T`. The `reified` modifier works only with `inline` functions because the compiler copies (inlines) the function body directly into every call site. At each call site the actual type argument is statically known, so the compiler substitutes `T` with that concrete class everywhere in the inlined body. That makes expressions like `T::class`, `is T`, and `T::class.java` legal. Without `inline` there is no call-site copy to substitute into, so the compiler reports an error: `Type parameter T cannot be reified because the function is not inline`. The cost is that the body is duplicated at each call site, so reified functions should stay small.

code

kotlin · 6 lines
kotlin
inline fun <reified T> Any.isA(): Boolean = this is T

fun main() {
    println("x".isA<String>()) // true  -> inlined as: "x" is String
    println(42.isA<String>())  // false -> inlined as: 42 is String
}

go deeper

for a junior

States that inline copies the body into the call site and that the concrete type is known there, so T can be substituted.

for a middle

Connects it to JVM erasure and names the exact unlocked operations (T::class, is T, as? T) and the compiler error.

for a senior

Explains the substitution happens per call site, the code-size trade-off, and when to avoid reified on large/hot functions.

for a principal

Frames reified as a compile-time monomorphization shim over an erased runtime, and reasons about API design vs. bytecode bloat across a library surface.

## The problem: type erasure On the JVM, generics use **type erasure**: at runtime `List<String>` and `List<Int>` are both just `List`, and a generic function's type parameter `T` carries no `Class` token. So inside an ordinary `fun <T> foo()` you cannot write `T::class`, `x is T`, or `T::class.java` — the compiler rejects them because the information is gone at runtime. ## How `inline` changes the picture The `inline` keyword tells the compiler to **copy the function body into each call site** instead of generating a real call. After inlining, the code lives where the function was *invoked*, and at an invocation the concrete type argument is **statically known**. ```kotlin inline fun <reified T> isInstance(value: Any): Boolean = value is T val ok = isInstance<String>("hi") // after inlining the compiler effectively produces: // val ok = "hi" is String ``` Because the body is pasted in with `T` replaced by the concrete `String`, the otherwise-illegal `value is T` becomes the perfectly legal `value is String`. ## What `reified` actually does `reified` is a modifier on a **type parameter** (`<reified T>`) that is only allowed when the function is `inline`. It instructs the compiler to substitute `T` with the real class at every call site. This unlocks: - `T::class` and `T::class.java` (KClass / Class tokens) - `value is T` and `value as? T` (runtime type checks/casts) - passing `T` to another reified function ## Why non-inline can't do it A non-inline function compiles to **one** shared method body. There is no per-call-site copy to substitute a concrete type into, and at runtime `T` is erased — so the compiler errors: *"Type parameter T cannot be reified because the function 'foo' is not inline."* Marking the function `inline` is the fix. ## The canonical use This is what powers ergonomic APIs such as `inline fun <reified T> Gson.fromJson(json: String): T`, letting you write `gson.fromJson<User>(json)` instead of passing `User::class.java` explicitly. ## Trade-off Inlining duplicates bytecode at every call site, so keep reified functions small and avoid them on hot mega-functions where code-size matters.

  • What exact compiler error appears if you write `reified` without `inline`?
    "Type parameter T cannot be reified because the function 'foo' is not inline" — the fix is to add the `inline` modifier.
  • Does `inline` alone make `T` reified?
    No. `inline` enables it, but you must also explicitly mark the type parameter `reified`. A plain inline generic still has an erased `T`.

It's like a mail-merge: the template (function) is copied into each letter (call site) with the real name (concrete type) filled in, so by the time it runs there's nothing generic left.

saying these in an interview costs you the question

  • Claiming reified works on any generic function regardless of inline
  • Saying inline is about performance only and ignoring the substitution mechanism
  • Thinking the JVM keeps the type at runtime for reified params (it's a compile-time substitution)
  • Confusing reified with passing a Class<T> parameter

context

open as a page

Show how the inline+reified pattern turns Gson's `fromJson(json, Class)` into `fromJson<T>(json)`. What gets generated at the call site?

level: middleimportance: must knowfreq 65%

basics

~20 s

You write a small inline extension with a reified type. Inside it you can use T::class.java, so you call the old fromJson(json, T::class.java) for the caller. At the call site T is replaced by the real class, so the user just writes fromJson<User>(json).

open as a page

Inside `inline fun <reified T> a()` you call another generic helper `b<T>()`. What must be true of `b` for this to compile, and why?

level: middleimportance: should knowfreq 35%

basics

~20 s

If b also needs the real type (it's reified), then b must itself be an inline reified function. A reified type can only be passed to another reified parameter, because only there is the concrete class still known.

open as a page

An inline function with a reified parameter also takes a lambda. How do `noinline`/`crossinline` and the inlining contract interact with reified, and what constrains where you can call such a function?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Reified needs the whole function inlined. Lambda parameters are inlined too by default; marking one noinline keeps that lambda as a real object but doesn't break reified, since the function body is still copied. crossinline forbids non-local returns from the lambda.

open as a page

You expose a widely-used `inline fun <reified T> decode(bytes: ByteArray): T` in a public library. What are the bytecode/ABI and binary-compatibility consequences, and how would you mitigate them?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Because it's inline, the body is copied into every place callers use it, so the real logic lands in their code. Changing the body forces recompilation and can bloat their binaries. Keep the inline part tiny and delegate the heavy work to a normal function.

open as a page