skip to content

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%

answer

  1. reified needs body inlined, not lambdas
  2. noinline = real Function object, still allows reified
  3. crossinline = no non-local return across context
  4. no ::reifiedFn / no reflection / no Java caller
  5. @PublishedApi for internal access

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.

solid answer

~50 s

`reified` depends only on the **function body** being inlined at the call site; it is independent of how each lambda parameter is treated. By default all lambda params of an `inline` function are themselves inlined. `noinline` opts a specific lambda out (so it becomes a real `Function` object you can store/pass on) — that's fine alongside reified. `crossinline` keeps the lambda inlined but bans **non-local returns** from it, needed when the lambda is invoked from another execution context (e.g. inside a `Runnable`). Because the function is `inline`, callers must be code the compiler can inline into: you cannot call a reified-inline function via reflection, you cannot reference it as a function value (`::fn`), and Java callers can't use the reified parameter. Public inline functions also can't touch `private`/`internal` members of the declaring class unless those are `@PublishedApi internal`, because the body is copied into foreign call sites.

code

kotlin · 7 lines
kotlin
inline fun <reified T> register(noinline factory: () -> T) {
    registry[T::class] = factory   // reified T works; factory kept as object
}

inline fun <reified T> runLater(crossinline body: () -> T) {
    executor.execute { log(T::class, body()) } // crossinline: no non-local return
}

go deeper

for a junior

Recognizes that lambdas in inline functions are inlined by default and that reified relates to the type parameter, not the lambda.

for a middle

Distinguishes noinline (real object) from crossinline (no non-local return) and confirms neither breaks reified.

for a senior

Explains the full inline contract — no reflection/function-ref, Java unusable, @PublishedApi — and why reified depends only on the body being inlined.

for a principal

Reasons about API surface design: when to expose reified-inline helpers vs. Class<T> overloads for Java interop, and the bytecode/ABI implications of @PublishedApi.

## Two independent axes When you have `inline fun <reified T> withX(block: () -> Unit)`, there are two separate concerns: 1. **The body is inlined** — this is what `reified` requires (T is substituted at the call site). 2. **Each lambda parameter is inlined** — separately controllable per lambda. Reification depends only on #1, so changing how a lambda is treated never disables reified. ## `noinline` By default every lambda param of an inline function is inlined (no `Function` object allocated, non-local return allowed). Mark a param `noinline` when you need it to behave as an ordinary value — store it, return it, or pass it to a non-inline function: ```kotlin inline fun <reified T> register(noinline factory: () -> T) { // factory is a real Function0<T>; can be stored in a map registry[T::class] = factory } ``` Here `reified T` still works (`T::class`) even though `factory` is not inlined. ## `crossinline` Use `crossinline` when a lambda is still inlined but is invoked from a **different execution context** (a nested lambda, a `Runnable`, another thread). It forbids non-local `return` out of the enclosing function, which would be unsafe across that boundary: ```kotlin inline fun <reified T> runLater(crossinline body: () -> T) { executor.execute { val value: T = body(); log(T::class, value) } } ``` ## Constraints the inline contract imposes Because the body (with T substituted) is copied into each call site, reified-inline functions inherit all inline limitations: - **No function reference / reflection**: you can't do `::register` or call it reflectively — there's no single shared method to point at when reified. - **Not callable from Java**: Java sees an erased generic with no reified substitution. - **`@PublishedApi`**: a `public` inline function calling `internal`/`private` members must expose them via `@PublishedApi internal`, since the body executes inside callers in other modules. - **Code size**: each call site duplicates the (possibly large) body — keep these functions thin. ## Bottom line `reified` and `noinline`/`crossinline` are orthogonal knobs: the first is about substituting `T` (needs the body inlined), the latter two are about how individual lambdas are emitted and which returns are legal.

  • Can you take a function reference (`::myReifiedFn`) to an inline reified function?
    No. Reified functions exist only as inlined call-site copies; there is no single callable method to reference, so `::` and reflection are disallowed.
  • Why might a public inline reified function need `@PublishedApi internal`?
    Its body is copied into call sites in other modules, so any `internal` members it touches must be marked `@PublishedApi internal` to remain accessible there.

saying these in an interview costs you the question

  • Claiming noinline or crossinline disables reified
  • Saying you can call a reified inline function via reflection or as a function reference
  • Believing crossinline is needed for reified to work
  • Ignoring @PublishedApi and accessing private members from a public inline body
  • Thinking Java callers can use the reified type parameter

context