Kotlin Inline Functions
When you pass a lambda to a function, Kotlin normally creates an object for that lambda at runtime. For frequently called higher-order functions, this creates extra work. The inline keyword tells the compiler to copy the function body (and any lambda bodies) directly into the call site — eliminating the overhead.
The Performance Problem
Normal higher-order function call:
measure { doWork() }
↓ compiler creates a Lambda object for { doWork() }
↓ function call overhead
↓ lambda invocation overhead
Inline function call:
inline fun measure(block: () -> Unit) { ... }
measure { doWork() }
↓ compiler COPIES the function body to the call site
↓ { doWork() } is also inlined — no Lambda object created
↓ same as writing the body manually — zero overhead
Defining an Inline Function
inline fun measureTime(label: String, block: () -> Unit) {
val start = System.currentTimeMillis()
block()
val elapsed = System.currentTimeMillis() - start
println("$label took ${elapsed}ms")
}
fun main() {
measureTime("Sorting") {
val list = (1..100_000).shuffled().sorted()
println("Sorted ${list.size} items")
}
}
// The compiler transforms this into roughly:
// val start = System.currentTimeMillis()
// val list = (1..100_000).shuffled().sorted()
// println("Sorted ${list.size} items")
// println("Sorting took ...ms")noinline: Exclude One Lambda
If a function has multiple lambdas and you want to inline some but not others, use noinline:
inline fun process(
block: () -> Unit,
noinline callback: () -> Unit // this lambda is NOT inlined
) {
block()
scheduleAsync(callback) // callback passed as an object — needed here
}
fun scheduleAsync(task: () -> Unit) {
// stores the lambda for later
}crossinline: Prevent Non-Local Returns
inline fun runSafely(crossinline block: () -> Unit) {
try {
block()
} catch (e: Exception) {
println("Error: ${e.message}")
}
}
fun main() {
runSafely {
println("Running safely")
// return ← ERROR: crossinline prevents return from outer function
}
}Non-Local Returns in Inline Functions
In a normal lambda, you cannot use return to exit the enclosing function. In an inlined lambda, you can — because the lambda code is literally placed inside the function:
inline fun findFirst(items: List, predicate: (Int) -> Boolean): Int? {
for (item in items) {
if (predicate(item)) return item // returns from findFirst
}
return null
}
fun main() {
val numbers = listOf(3, 7, 12, 5, 20, 8)
val firstEven = findFirst(numbers) { it % 2 == 0 }
println("First even: $firstEven") // 12
} When to Use inline
Use inline when:
✓ The function accepts lambda parameters
✓ It is called very frequently (in loops, hot paths)
✓ You need non-local returns in lambdas
✓ You use reified type parameters (next topic)
Don't use inline when:
✗ The function body is very large (code size bloats)
✗ No lambda parameters exist (no benefit)
✗ The lambda is stored for later use (use noinline)
Practical Example: Retry Logic
inline fun retry(times: Int, block: (attempt: Int) -> Boolean) {
for (attempt in 1..times) {
println("Attempt $attempt...")
val success = block(attempt)
if (success) {
println("Succeeded on attempt $attempt")
return
}
}
println("All $times attempts failed")
}
fun main() {
var counter = 0
retry(5) { attempt ->
counter++
attempt == 3 // succeeds on the 3rd attempt
}
}Output:
Attempt 1...
Attempt 2...
Attempt 3...
Succeeded on attempt 3