Unlocking Kotlin's inline and reified
Introduction
Kotlin is packed with powerful features that help developers write expressive and efficient code, but few are as misunderstood (or underused) as the inline keyword and its companion, reified. At first glance, they might seem like advanced compiler tricks reserved for library authors. In reality, they solve everyday problems: eliminating lambda overhead, enabling type-safe generic functions, and making your code both cleaner and more performant. In this article, we'll break down exactly what inline and reified do, why they exist, and when you should reach for them.
What Is the inline Keyword?
The inline keyword is used to optimize higher-order functions by reducing the runtime overhead associated with function calls, particularly when lambda expressions are involved. When a function is marked as inline, the Kotlin compiler substitutes the function body directly into the places where the function is called. This substitution eliminates the need to allocate memory for function objects and avoids the overhead of lambda invocation.
How Inlining Works Under the Hood
Normally, passing a lambda as a parameter involves creating an object to represent the lambda and invoking its method. This can introduce performance overhead, especially in performance-critical scenarios. Marking a function as inline eliminates this overhead by inserting the actual function body and lambda code directly into the call site. Consider the following example:
private inline fun inlineFun(lambda: () -> Unit) {
println("COMPUTING started")
lambda() // The code from the lambda will be copied here
println("Computing ended")
}
fun main() {
inlineFun { println("Performing heavy computing") }
}
In this example, the inlineFun function is marked as inline. The compiler will replace the call to inlineFun with its body, including the lambda code, resulting in fewer object allocations and better runtime performance.
Performance Benefits: Lambda Overhead Explained
Without using inline, every time you pass a lambda to a higher-order function, the compiler must create a function object on the heap to represent that lambda. This includes:
- Object Allocation: A new object is created on the heap to hold the lambda's code.
- Memory Overhead: This object consumes memory.
-
Virtual Method Call: Invoking the lambda requires a virtual method call (
invoke()), which is slightly slower than a direct method call.
In a function that is called frequently, especially within a loop, this overhead of creating many small Function objects can add up and put pressure on the garbage collector. The inline keyword solves this completely — when a function is inlined, the compiler doesn't create a Function object for the lambda. Instead, it pastes the body of the lambda directly where it is called.
// Inline Function
private inline fun inlineFun(lambda: () -> Unit) {
println("COMPUTING started")
lambda() // The code from the lambda will be copied here
println("Computing ended")
}
// Non Inline Function
private fun nonInlined(lambda: () -> Unit) {
println("COMPUTING started")
lambda() // This is a virtual call to a Function object
println("Computing ended")
}
fun main() {
inlineFun { println("Performing heavy computing") }
nonInlined { println("Performing heavy computing") }
}
// What the compiler generates
fun main_compiled() {
// For nonInlined, a Function object is created
println("COMPUTING started")
Function0 { println("Performing heavy computing") }.invoke()
println("Computing ended")
// For inline Function, the code is copied directly
println("COMPUTING started")
println("Performing heavy computing") // No object, no virtual call
println("Computing ended")
}
Introducing reified: Retaining Type Information at Runtime
Another powerful feature unlocked by inline is the ability to use reified type parameters. In Kotlin, generics are erased at runtime due to type erasure in the JVM, which means you cannot access the type of a generic parameter T at runtime (e.g., you can't do T::class). However, by marking a function as inline, you can use the reified keyword to retain type information at runtime. This is particularly useful for type-checking or casting.
private inline fun <reified T> isInstance(value: Any): Boolean {
return value is T
}
fun main() {
println(isInstance<String>("This is string")) // true
println(isInstance<Int>("This is Int")) // false
}
In this example, reified allows the type parameter T to be used in a type-checking operation (is T) at runtime. This is extremely useful for creating clean, type-safe APIs, especially in libraries. A common use case in Android is finding a Fragment by its type: findFragment<MyFragment>().
inline + reified in Action: Real-World Examples
When a function is marked as inline, the Kotlin compiler substitutes the function's code directly at the call site. If a type parameter is declared as reified, it ensures that the type information is available during this inlining process, allowing the type to be accessed and manipulated at runtime. One of the most common use cases of reified is simplifying generic types in scenarios where type information is required at runtime, such as reflection, type casting, or creating instances of classes.
Benefits of Using reified
When working with generics in Kotlin, using the reified keyword in inline functions unlocks powerful capabilities that are otherwise restricted due to type erasure. Here are the key advantages:
- Type Safety: Allows for safer code by reducing the need for unchecked casts.
-
Cleaner Syntax: Removes the requirement for explicitly passing
Classobjects as parameters. - Runtime Reflection: Enables easier runtime operations like checking types or accessing class metadata.
inline fun <reified T> List<Any>.filterByType(): List<T> {
return this.filterIsInstance<T>()
}
fun main() {
val mixedList = listOf(1, "Class A", 2.5, "Programming")
val stringList = mixedList.filterByType<String>()
println(stringList) // Outputs: [Class A, Programming]
}
Here, the filterByType function retains type information through reified, enabling type-specific filtering.
Limitations of reified and Inline
Inline
- Code bloat — large functions copied at every call site increase binary size and can cause CPU cache misses.
- Large functions — substantial logic should stay non-inlined to keep your APK lighter and runtime efficient.
-
No overriding —
inlinefunctions are implicitly final, making them unsuitable for polymorphic or inheritance-based designs. - No lambdas, no benefit — without lambda parameters, the JVM's JIT compiler handles inlining better than doing it manually.
- Avoid with recursion — recursive functions cannot be inlined; the compiler will warn you if you try.
-
Limited use as private functions — private
inlinefunctions offer little advantage if they aren't reused across multiple call sites.
Reified
- Only works inside
inlinefunctions — it cannot be used for general-purpose generics outside that context. - The JVM's type erasure is why this limitation exists; generic types are lost at runtime by default.
- Must be used judiciously — forces the function to be
inlineand can cause code bloat if overused. - Best suited for utility functions or framework code where type introspection and flexibility are genuinely needed.
Key Takeaways
Inline
- Substitutes the function body and lambdas directly at the call site, cutting down memory allocation and runtime overhead.
- Particularly effective when lambdas are used frequently, as it avoids the cost of object creation for each call.
- Unlocks the ability to use
reifiedgenerics, which is otherwise impossible in regular functions. - Overusing it can lead to code bloat — the more places a large function is inlined, the larger the compiled output becomes.
- Apply it judiciously: best suited for small, frequently called higher-order functions rather than large or rarely used ones.
reified
- Allows access to generic type information at runtime, which is normally erased by the JVM.
- Makes type-based operations safer, cleaner, and more readable.
- Best suited for runtime reflection or type manipulation scenarios.
- Only works inside
inlinefunctions — it cannot be used in regular generic programming.
Conclusion
The inline keyword and reified generics are among Kotlin's most powerful yet nuanced features. When applied thoughtfully, they eliminate boilerplate, improve runtime performance, and enable type-safe operations that would otherwise require verbose workarounds. However, like any optimization tool, they come with trade-offs such as code bloat, design constraints, and limited applicability outside specific contexts. The key is knowing when to reach for them and when to step back. As you grow more comfortable with these features, you'll find they naturally slot into the right places in your codebase, making your Kotlin code not just more efficient, but more expressive and elegant.
Top comments (0)