Learn Kotlin with me — Inline Functions
In this blog, we will explore Inline functions. Inline functions are some of the most asked questions in Android Development Interviews. Also, they are a part of most of the important files in Android Development. For example- inline functions are used in declarations of a viewModel. In this blog, we will understand how and when should we use them.
What is an Inline Function?
In Kotlin, higher-order functions (functions that take another function as a parameter) create runtime overhead due to function object creation and indirect calls. inline functions solve this by replacing function calls with the actual function body at compile time.
Basic Example of an Inline Function
inline fun inlineFunction(action: () -> Unit) {
println("Before action")
action()
println("After action")
}fun main() {
inlineFunction { println("Inside action") }
}Without inline, the lambda { println("Inside action") } creates a function object at runtime.
With inline, the compiler replaces inlineFunction() with:
println("Before action")
println("Inside action")
println("After action")Why Use Inline Functions?
- Performance Boost: Reduces function call overhead.
- Avoids Object Creation: No extra lambda function objects.
- Improves Readability: Makes code more readable when used in small utility functions.
- No Extra Function Call Overhead: Since the function body is inserted directly at the call site, the runtime doesn’t need to perform a typical call.
- Reduced Lambda Object Creation: When inline functions accept lambda expressions as parameters, the lambda code is also inlined. This means that no extra objects are created for the lambda, avoiding additional memory allocations.
Features of Inline functions
- Non-Local Returns
One of the more striking features of inline functions is the support for non-local returns from lambda expressions. In a normal (non-inline) higher-order function, you cannot return from the enclosing function inside a lambda. However, with inline functions, you can perform a return from the outer function within the lambda, because the lambda’s code is inlined into the calling function.
Consider this example:
inline fun execute(action: () -> Unit) {
action()
}fun demo() {
execute {
// This non-local return exits 'demo'
return
}
println("This line is never reached.")
}Here, the return inside the lambda causes the entire demo() function to exit, thanks to inlining. This capability is extremely useful for writing concise control-flow logic within higher-order functions.
- The
noinlineModifier
While inline functions typically inline every lambda parameter, there are scenarios when you may want to prevent inlining for specific lambda parameters. The noinline modifier allows you to opt-out of those parameters. This is useful when:
- Passing Lambdas to Other Functions: If you plan to store or manipulate a lambda (for instance, assign it to a variable), it must not be inlined.
- Reducing Code Size: Inlining large lambda expressions might lead to code bloat; marking them as
noinlinecan help keep the compiled bytecode smaller.
inline fun process(
inlineAction: () -> Unit,
noinline deferredAction: () -> Unit
) {
inlineAction() // This lambda is inlined.
deferredAction() // This lambda is not inlined.
}- The
crossinlineModifier
Sometimes, you want your lambda parameters to be inlined, yet you need to ensure that they don’t perform non-local returns. That’s where crossinline comes in. By marking a lambda parameter as crossinline, you guarantee that the lambda’s code cannot use the return statement to exit from the enclosing function.
Example:
inline fun executeCrossinline(crossinline action: () -> Unit) {
val runnable = Runnable { action() }
Thread(runnable).start()
}In this case, even though action is inlined, using crossinline prevents any accidental non-local return, which could otherwise lead to unpredictable control flow in multi-threaded scenarios.
- Reified Type Parameters
Inline functions have one more standout feature: they can have reified type parameters. Normally, due to type erasure, the specific type information of generic parameters is not available at runtime. However, by marking a type parameter as reified within an inline function, the compiler retains its type information. This allows for type checks and casts without resorting to reflection.
Example:
inline fun <reified T> isInstance(value: Any): Boolean {
return value is T
}fun main() {
println(isInstance<String>("Hello")) // Prints: true
println(isInstance<Int>("Hello")) // Prints: false
}Here, T is reified, so the function can directly check if value is of type T, enhancing both type safety and expressiveness.
Key Benefits at a Glance
- Performance Optimization: By eliminating function calls and lambda object creation, inline functions can significantly reduce runtime overhead — especially in performance-critical code such as tight loops.
- Enhanced Control Flow: Support for non-local returns allows inline functions to streamline code by removing the need for additional control structures.
- Flexible Lambda Handling: With the
noinlineandcrossinlinemodifiers, you have fine-grained control over which lambda parameters are inlined and how they behave. - Powerful Generic Capabilities: Reified type parameters enable inline functions to retain type information, facilitating operations like type checking without extra reflection costs.
If you want to understand more, here are some videos for you:
Non-local returns in Inline Functions:
