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

Leave a Comment

Your email address will not be published. Required fields are marked *