Jetpack Compose has fundamentally changed how Android developers build UI. Its declarative model, composable functions, and Kotlin-first approach dramatically reduce boilerplate and improve readability.
However, with that power comes subtle performance pitfalls.
One of the most common—and least understood—is inline lambdas that capture state, which can lead to unnecessary recomposition, reduced UI performance, and even logic bugs when handled incorrectly.
In this article, you will learn:
- How recomposition works in Jetpack Compose
- Why inline lambdas can trigger recomposition
- When inline lambdas are perfectly safe
- Production-ready Kotlin patterns to avoid performance regressions
What Is Recomposition in Jetpack Compose?
Recomposition is the process where Compose re-executes composable functions to update the UI when something changes.
Recomposition occurs when:
- Observed state changes
- A composable parameter is considered different from the previous run
This second point is critical—and often overlooked.
In Compose, lambdas are parameters, and parameters are compared by identity and stability.
The Hidden Cost of Inline Lambdas in Compose
Consider this common pattern:
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val count by viewModel.count.collectAsState()
CounterButton(
onClick = { viewModel.increment(count) }
)
}
At first glance, this looks clean and idiomatic.
Why This Causes Extra Recomposition
- The lambda
{ viewModel.increment(count) }capturescount - Every time
countchanges:- A new lambda instance is created
- Compose detects a changed parameter
CounterButtonmust recompose
Even if CounterButton’s UI does not depend on count, Compose cannot skip it.
In large UI trees or lists, this adds measurable overhead.
Why This Matters for Real-World Android Apps
In production apps:
- Buttons are nested deep inside layouts
- Lazy lists reuse composables hundreds of times
- Recomposition frequency directly affects scroll smoothness
Unchecked inline lambdas can:
- Increase recomposition counts
- Reduce Compose’s ability to skip work
- Create performance issues that are hard to diagnose
The Common Mistake: remember Used Incorrectly
Many developers attempt to “fix” this problem like this:
val onClick = remember {
{ viewModel.increment(count) }
}
Why This Is Dangerous
- The lambda becomes stable
- But it captures only the initial value of
count - Future updates are ignored
- Your UI logic is now incorrect
This is one of the most subtle Jetpack Compose bugs—and it often passes code review.
The Correct Solution: rememberUpdatedState
The correct, production-safe approach is to stabilize the lambda without freezing state.
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val count by viewModel.count.collectAsState()
val latestCount by rememberUpdatedState(count)
CounterButton(
onClick = {
viewModel.increment(latestCount)
}
)
}
Why This Pattern Works
- The lambda identity remains stable
- Compose can skip recomposition
- The lambda always reads the latest state
- No stale values, no unnecessary work
This pattern is ideal for long-lived callbacks, animations, effects, and event handlers.
The Best Option: Avoid Capturing State Entirely
Whenever possible, prefer function references:
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
CounterButton(onClick = viewModel::increment)
}
Benefits
- Function references are stable
- No state capture
- Maximum recomposition skipping
- Cleaner architecture and testability
This is the gold standard for Compose event handling.
When Inline Lambdas Are Actually Safe
Not all inline lambdas are bad.
@Composable
fun ProductRow(
product: Product,
onAddToCart: (Product) -> Unit
) {
Button(
onClick = { onAddToCart(product) }
) {
Text("Add to cart")
}
}
This is safe because:
productis already a parameter- The lambda does not capture mutable state
- Recomposition happens only when it should
The problem is not inline lambdas, but inline lambdas that capture changing state unintentionally.
Jetpack Compose Performance Best Practices (Quick Checklist)
- Inline lambdas are safe if they do not capture mutable state
- Avoid capturing frequently changing values
- Never use
remember {}to freeze stateful lambdas - Use
rememberUpdatedStatefor stable callbacks - Prefer function references when possible
- Let Compose skip work—do not fight the compiler
Final Thoughts
Jetpack Compose is fast—but only if you let it be.
Inline lambdas are convenient.
Stable lambdas are performant.
Correct lambdas are both.

