Skip to content
Inline Functions and Reified Generics
step 1/6

Reading — step 1 of 6

Learn

~3 min readFunctional Patterns

inline functions are a Kotlin-specific optimization that matters more than it sounds. They eliminate runtime overhead of higher-order functions AND enable a unique feature: reified type parameters.

Why inline

Normally, calling a function with a lambda parameter creates a Function object at runtime — small but non-zero overhead:

fun <T> measureTime(block: () -> T): Pair<T, Long> {
    val start = System.currentTimeMillis()
    val result = block()
    return result to (System.currentTimeMillis() - start)
}

When you call measureTime { doWork() }, the JVM creates a Function0 object holding the closure. For tight loops or hot paths, this adds up.

inline tells the compiler to copy the function body — and the lambda body — directly to the call site:

inline fun <T> measureTime(block: () -> T): Pair<T, Long> {
    val start = System.currentTimeMillis()
    val result = block()
    return result to (System.currentTimeMillis() - start)
}

Now each call site gets the body inlined — no Function object, no virtual call. The JIT can optimize the merged code together.

Reified type parameters

Kotlin generics are erased on the JVM (same as Java). At runtime, T is gone:

fun <T> isInstance(obj: Any): Boolean {
    return obj is T   // ✗ compile error — can't check erased type
}

But INLINE functions can use reified to keep the type:

inline fun <reified T> isInstance(obj: Any): Boolean {
    return obj is T   // ✓ works — T is reified at compile time
}

isInstance<String>("hi")    // true
isInstance<Int>("hi")        // false

Reified types unlock patterns Java can't express:

inline fun <reified T> List<*>.filterIsInstanceOf(): List<T> {
    return filter { it is T }.map { it as T }
}

inline fun <reified T : Any> jsonDecode(json: String): T {
    return Gson().fromJson(json, T::class.java)
}

No Class<T> parameter needed — the compiler stamps in the type at each call site.

When NOT to inline

  • Large functions — inlining bloats the binary at every call site.
  • Recursive functions — can't inline themselves.
  • Functions WITHOUT lambda parameters — JVM JIT inlines well on its own. Extra inline here just bloats code.

The sweet spot: small higher-order functions where lambda overhead matters or you need reified types.

crossinline and noinline

When inlining gets nuanced:

  • crossinline — the lambda can't return from the calling function (only its own).
  • noinline — this lambda parameter is NOT inlined (when only some need to be).
inline fun runBoth(
    crossinline a: () -> Unit,    // can't break out of caller
    noinline b: () -> Unit         // not inlined; can be stored
) {
    Thread { a() }.start()         // a is captured into another scope
    val later = b                   // b is referenced as a Function object
    later()
}

Don't memorize these — the compiler tells you when you need them.

Common mistakes

  • Marking everything inline — bloats binary, slows compilation.
  • reified without inline — compile error. Reification requires inlining.
  • Recursive inline functions — can't inline themselves; the compiler errors.
  • Storing inlined lambdas in variables — requires noinline on that parameter.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…