Reading — step 1 of 6
Learn
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
inlinehere 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'treturnfrom 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.
reifiedwithoutinline— compile error. Reification requires inlining.- Recursive
inlinefunctions — can't inline themselves; the compiler errors. - Storing inlined lambdas in variables — requires
noinlineon that parameter.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…