Skip to main content

Kotlin Inline Functions

In Kotlin, when you use higher-order functions and lambdas, there's a hidden cost: additional memory allocations and virtual function calls. Inline functions provide a way to eliminate this overhead, making your code more efficient. This is especially important for performance-sensitive applications.

What Are Inline Functions?

An inline function is a function marked with the inline keyword. When you call an inline function that accepts lambdas as parameters, the compiler doesn't create function objects for those lambdas. Instead, it "inlines" both the function's body and the lambdas' bodies directly at the call site.

Basic Syntax

kotlin
inline fun functionName(lambdaParameter: () -> Unit) {
// function body
}

Why Use Inline Functions?

Before diving deeper, let's understand why we need inline functions in the first place:

  1. Performance Improvement: Eliminates lambda object creation overhead
  2. Memory Efficiency: Reduces heap allocations
  3. Enables Non-local Returns: Allows return statements inside lambdas to exit the caller function
  4. Special Features: Enables features like crossinline and noinline for fine-grained control

Basic Example of Inline Functions

Let's compare a regular higher-order function with its inline counterpart:

Without Inline

kotlin
fun regularHigherOrderFunction(action: () -> Unit) {
println("Before action")
action()
println("After action")
}

fun main() {
regularHigherOrderFunction {
println("This is the action")
}
}

Output:

Before action
This is the action
After action

With Inline

kotlin
inline fun inlineHigherOrderFunction(action: () -> Unit) {
println("Before action")
action()
println("After action")
}

fun main() {
inlineHigherOrderFunction {
println("This is the action")
}
}

Output:

Before action
This is the action
After action

Both functions produce the same output, but the inline version doesn't create a lambda object at runtime, making it more efficient.

How Inlining Works

When the Kotlin compiler processes an inline function call, it effectively replaces the function call with the actual code inside the function. For the above example, the compiled code would look something like:

kotlin
fun main() {
// This is what happens after inlining:
println("Before action")
println("This is the action")
println("After action")
}

Notice there's no function call overhead and no lambda object creation!

Non-local Returns

One powerful feature of inline functions is non-local returns. In regular lambdas, you can't use a bare return statement to exit the outer function, but in inline functions, you can:

kotlin
fun processNumbers(numbers: List<Int>, action: (Int) -> Unit) {
for (number in numbers) {
action(number)
}
}

inline fun processNumbersInline(numbers: List<Int>, action: (Int) -> Unit) {
for (number in numbers) {
action(number)
}
}

fun main() {
val numbers = listOf(1, 2, 3, 4, 5)

// With regular function - can't use non-local return
processNumbers(numbers) { num ->
if (num == 3) {
// This return only exits the lambda
return@processNumbers
}
println("Processing $num")
}
println("After regular processing")

// With inline function
processNumbersInline(numbers) { num ->
if (num == 3) {
// This return exits the main function!
return
}
println("Processing inline $num")
}
println("This line is never reached if numbers contains 3")
}

Output:

Processing 1
Processing 2
Processing 4
Processing 5
After regular processing
Processing inline 1
Processing inline 2

The execution exits the main function when it encounters the return inside the lambda passed to the inline function.

Controlling Inlining Behavior

Kotlin provides two modifiers to fine-tune how inlining works:

1. noinline

Sometimes, you may want to prevent a specific lambda parameter from being inlined. You can use the noinline modifier for this:

kotlin
inline fun performOperations(
inlined: () -> Unit,
noinline notInlined: () -> Unit
) {
println("Before operations")
inlined()
notInlined()
println("After operations")
}

fun main() {
performOperations(
{ println("This lambda is inlined") },
{ println("This lambda is NOT inlined") }
)
}

Output:

Before operations
This lambda is inlined
This lambda is NOT inlined
After operations

The noinline parameter will still create a function object, while the regular parameter gets inlined.

2. crossinline

When you need to ensure that a lambda passed to an inline function doesn't perform non-local returns, use the crossinline modifier:

kotlin
inline fun executeWithCallback(crossinline callback: () -> Unit) {
val runnable = Runnable {
callback() // Using callback in another context
}
runnable.run()
}

fun main() {
executeWithCallback {
println("Callback executed")
// return // This would cause a compilation error
}
}

Output:

Callback executed

The crossinline modifier prevents you from using non-local returns in the lambda because the lambda is being called from a different context (in this case, the Runnable).

Real-world Applications of Inline Functions

1. Custom Control Structures

Inline functions are perfect for creating custom control structures:

kotlin
inline fun executeIfTrue(condition: Boolean, action: () -> Unit) {
if (condition) {
action()
}
}

fun main() {
val userLoggedIn = true

executeIfTrue(userLoggedIn) {
println("Welcome back!")
}
}

Output:

Welcome back!

2. Resource Management

Inline functions are excellent for ensuring resources are properly closed:

kotlin
inline fun <T> withResource(resource: AutoCloseable, block: (AutoCloseable) -> T): T {
try {
return block(resource)
} finally {
resource.close()
}
}

class SimpleResource : AutoCloseable {
fun performOperation() = println("Operation performed")
override fun close() = println("Resource closed")
}

fun main() {
val result = withResource(SimpleResource()) { resource ->
(resource as SimpleResource).performOperation()
"Operation result"
}

println("Result: $result")
}

Output:

Operation performed
Resource closed
Result: Operation result

3. Measuring Execution Time

Create a utility function to measure how long a block of code takes to execute:

kotlin
inline fun measureTimeMillis(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}

fun main() {
val time = measureTimeMillis {
// Simulate some work
var counter = 0
for (i in 1..1_000_000) {
counter += i
}
println("Work completed, counter = $counter")
}

println("Execution took $time ms")
}

Output (will vary):

Work completed, counter = 500000500000
Execution took 24 ms

Performance Considerations

While inline functions improve performance by eliminating lambda object creation and virtual function calls, they're not always the best choice:

  1. Code Size: Inlining increases bytecode size since the function body is copied to each call site
  2. Compilation Time: More complex inline functions can slow down compilation
  3. Appropriate Use: Best for small, frequently called functions that take lambdas

As a rule of thumb, consider inlining when:

  • The function is small
  • It takes lambda parameters
  • It's called frequently
  • Performance is critical

Summary

Kotlin's inline functions provide a powerful way to improve performance when working with higher-order functions and lambdas. Key points to remember:

  • Inline functions eliminate the overhead of lambda object creation
  • They enable non-local returns from lambdas
  • Use noinline to prevent specific parameters from being inlined
  • Use crossinline when a lambda needs to be used in a non-local context
  • Inline functions are ideal for creating custom control structures and resource management patterns
  • Consider the trade-off between performance gain and increased code size

Practice Exercises

  1. Create an inline function called retry that takes a number of attempts and a lambda, executing the lambda until it succeeds or runs out of attempts
  2. Implement an inline function for a simple caching mechanism
  3. Create a custom iteration function that processes elements only if they meet certain criteria
  4. Benchmark the performance difference between regular and inline functions for a specific use case in your application

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)