Jetpack Compose Recomposition: Why Inline Lambdas Can Hurt Performance (And How to Fix It)

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) } captures count
  • Every time count changes:
    • A new lambda instance is created
    • Compose detects a changed parameter
    • CounterButton must 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:

  • product is 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 rememberUpdatedState for 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.

    jetpackcompose

    Top Rated Book In Amazon

    Leave a Comment

    Your email address will not be published. Required fields are marked *