Jetpack Compose uses state-driven UI, but a high recomposition count is not automatically a performance defect. A small composable may recompose cheaply, while an expensive layout, image operation, or I/O task may cause visible jank.
Begin with real measurement
Reproduce the problem in a release-like build with R8 enabled. Measure a real user journey such as cold start, opening a detail screen, scrolling a feed, or changing a filter.
Use system traces, Layout Inspector, Macrobenchmark, and Baseline Profiles to identify whether time is spent in composition, layout, drawing, image work, or I/O.
Keep state reads close to the work
If a screen-level composable reads rapidly changing state and passes it through a large tree, more content may be reconsidered than necessary. Move reads closer to the element that needs them or defer frequently changing reads with lambda-based modifiers.
Give lazy content stable identity
LazyColumn {
items(
items = articles,
key = { article -> article.id },
) { article ->
ArticleRow(article)
}
}
Stable keys help Compose preserve item identity when content changes position.
Use stability tools carefully
Do not add @Stable or @Immutable unless the type actually satisfies the contract. False stability annotations can produce stale UI.
Measure first, change the smallest relevant part, and measure again.
Sources: Compose performance, Baseline Profiles, Strong skipping