How to Debug Recomposition in Jetpack Compose

Unnecessary recomposition — Compose re-running a function whose output hasn’t actually changed — is the most common source of jank in a Compose app. Here’s how to actually see it happening, rather than guessing.

Method 1: The Layout Inspector

Android Studio’s Layout Inspector (Tools > Layout Inspector, with a debug build running) has a “Recomposition Counts” column you can enable from its settings gear icon. It shows, per composable, how many times it has recomposed and how many times it has skipped recomposing since the app started. A number that climbs continuously while nothing on screen visibly changes is your first clue.

Method 2: A Manual Recomposition Counter

For quick local debugging without switching tools, a small helper that logs on every recomposition is often faster:

@Composable
fun LogRecomposition(tag: String) {
    val count = remember { intArrayOf(0) }
    count[0]++
    Log.d("Recompose", "$tag: ${count[0]}")
}

@Composable
fun ProductCard(product: Product) {
    LogRecomposition("ProductCard-${product.id}")
    // ...
}

Watch Logcat while interacting with the screen. If ProductCard for every item in a list logs on every scroll frame, something upstream is invalidating state it shouldn’t.

Common Causes, in Order of Likelihood

  • An unstable parameter type. Passing a plain List<T> or a class with a var property makes Compose treat that parameter as unstable, so it can’t skip recomposition even when the value is identical. See our guide on Compose stability.
  • Reading state too high in the tree. If a parent composable reads a value that changes often, every child underneath it recomposes too, even children that don’t use that value — because the whole function re-runs. Move the read down to the smallest composable that actually needs it.
  • A new lambda instance on every recomposition. An inline lambda like onClick = { doThing(item) } is a new object each time unless its captured values are stable, which can itself force a child to be treated as changed.
  • A Practical Workflow

    Start with the Layout Inspector to find which composable is recomposing too often. Then check its parameters against the stability rules to find why. Fixing stability issues at the data-class level (adding @Immutable, switching a List to a kotlinx.collections.immutable type) usually fixes many composables at once, since instability tends to propagate from a shared model class rather than being unique to one screen.