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