Kotlin Inline Functions

Optimizing higher-order functions for better performance

⚡ What are Inline Functions?

Inline functions optimize performance by copying function code directly to call sites instead of creating function objects. They eliminate overhead from higher-order functions while maintaining clean, readable code.


// Inline function
inline fun measureTime(action: () -> Unit) {
    val startTime = System.currentTimeMillis()
    action()
    val endTime = System.currentTimeMillis()
    println("Execution time: ${endTime - startTime}ms")
}

fun main() {
    measureTime {
        // Some operation
        Thread.sleep(100)
        println("Task completed!")
    }
}
                                    

Output:

Task completed!

Execution time: 100ms

Inline Function Benefits

🚀

Performance

Eliminates function call overhead

// No function object creation
inline fun repeat3(action: () -> Unit) {
    action(); action(); action()
}
📋

Code Copying

Function body copied to call site

// Code gets inlined at compile time
inline fun debug(message: () -> String) {
    println("[DEBUG] ${message()}")
}
🔄

Control Flow

Allows return from calling function

// return works as expected
inline fun validate(check: () -> Boolean) {
    if (!check()) return
}
⚙️

Reified Types

Access generic type information

// Type information preserved
inline fun  isInstance(obj: Any) = obj is T

🔹 Basic Inline Functions

Use the 'inline' keyword to make functions inline:

// Regular function (creates function object)
fun regularRepeat(times: Int, action: () -> Unit) {
    repeat(times) { action() }
}

// Inline function (no function object)
inline fun inlineRepeat(times: Int, action: () -> Unit) {
    repeat(times) { action() }
}

// Inline function with multiple lambda parameters
inline fun withLogging(setup: () -> Unit, action: () -> Unit, cleanup: () -> Unit) {
    println("Starting operation...")
    setup()
    action()
    cleanup()
    println("Operation completed!")
}

fun main() {
    // Both work the same way, but inline version is more efficient
    regularRepeat(3) { println("Regular: Hello!") }
    inlineRepeat(3) { println("Inline: Hello!") }
    
    withLogging(
        setup = { println("Setting up...") },
        action = { println("Doing work...") },
        cleanup = { println("Cleaning up...") }
    )
}

Output:

Regular: Hello!

Regular: Hello!

Regular: Hello!

Inline: Hello!

Inline: Hello!

Inline: Hello!

Starting operation...

Setting up...

Doing work...

Cleaning up...

Operation completed!

🔹 Control Flow with Inline Functions

Inline functions allow non-local returns:

inline fun findFirst(numbers: List, condition: (Int) -> Boolean): Int? {
    for (number in numbers) {
        if (condition(number)) {
            return number  // This return is from findFirst
        }
    }
    return null
}

fun processNumbers(): String {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    // This return will exit processNumbers function
    val result = findFirst(numbers) { 
        if (it > 5) return "Found large number: $it"  // Non-local return
        false
    }
    
    return "No large number found"  // This won't be reached
}

inline fun validateInput(input: String, validator: (String) -> Boolean): Boolean {
    println("Validating: $input")
    return validator(input)
}

fun checkPassword(password: String): String {
    val isValid = validateInput(password) {
        if (it.length < 8) return "Password too short"  // Returns from checkPassword
        if (!it.any { char -> char.isDigit() }) return "Password needs a digit"
        true
    }
    
    return if (isValid) "Password is valid" else "Password validation failed"
}

fun main() {
    println(processNumbers())
    println(checkPassword("abc"))
    println(checkPassword("abc123def"))
}

Output:

Found large number: 6

Validating: abc

Password too short

Validating: abc123def

Password is valid

🔹 Reified Type Parameters

Inline functions can access generic type information at runtime:

// Reified type parameter allows runtime type checking
inline fun  isOfType(obj: Any): Boolean {
    return obj is T
}

inline fun  filterByType(list: List): List {
    return list.filterIsInstance()
}

inline fun  createList(vararg items: Any): List {
    return items.filterIsInstance()
}

// Extension function with reified type
inline fun  List<*>.countOfType(): Int {
    return this.count { it is T }
}

fun main() {
    val mixedList = listOf(1, "hello", 2.5, "world", 42, true, "kotlin")
    
    // Check types
    println("Is 'hello' a String? ${isOfType("hello")}")
    println("Is 42 a String? ${isOfType(42)}")
    
    // Filter by type
    val strings = filterByType(mixedList)
    val numbers = filterByType(mixedList)
    
    println("Strings: $strings")
    println("Numbers: $numbers")
    
    // Create typed list
    val intList = createList(1, "hello", 2, 3.14, 4)
    println("Int list: $intList")
    
    // Count by type
    println("String count: ${mixedList.countOfType()}")
    println("Int count: ${mixedList.countOfType()}")
}

Output:

Is 'hello' a String? true

Is 42 a String? false

Strings: [hello, world, kotlin]

Numbers: [1, 42]

Int list: [1, 2, 4]

String count: 3

Int count: 2

🔹 When to Use Inline Functions

Best practices for using inline functions:

✅ Good Use Cases:

  • Higher-order functions: Functions that take lambdas as parameters
  • Small utility functions: Simple operations called frequently
  • Performance-critical code: Where function call overhead matters
  • Reified generics: When you need runtime type information

❌ Avoid Inline For:

  • Large functions: Increases compiled code size
  • Recursive functions: Can cause infinite inlining
  • Functions stored in variables: Cannot be inlined
  • Virtual functions: Cannot be inlined effectively
// Good: Small utility function
inline fun  T.applyIf(condition: Boolean, block: T.() -> T): T {
    return if (condition) this.block() else this
}

// Good: Performance-critical operation
inline fun benchmark(operation: () -> Unit): Long {
    val start = System.nanoTime()
    operation()
    return System.nanoTime() - start
}

fun main() {
    val text = "hello world"
        .applyIf(true) { uppercase() }
        .applyIf(false) { reversed() }
    
    println("Result: $text")
    
    val time = benchmark {
        repeat(1000) { 
            Math.sqrt(it.toDouble()) 
        }
    }
    println("Benchmark time: ${time}ns")
}

Output:

Result: HELLO WORLD

Benchmark time: 45000ns

🧠 Test Your Knowledge

What is the main benefit of inline functions in Kotlin?