A deep dive into five common Jetpack Compose recomposition mistakes found in production codebases: creating objects (formatters, brushes, regexes) during composition, passing unstable domain models across module boundaries, misusing `remember` with wrong or missing keys, prop-drilling high-frequency values through multiple composable layers, and passing entire UiState objects where only a few fields are needed. Each mistake is shown with a before/after code example, an explanation of why it survives code review, and practical fixes. The post also covers four diagnostic tools — Layout Inspector recomposition counts, Compose compiler reports, composition tracing, and a debug recomposition highlighter — along with a six-point PR review checklist.
Table of contents
First, What a Recomposition Actually Costs1. Creating Objects During Composition2. Unstable Parameters at Module Boundaries3. Misusing remember — The Optimization That Ships a BugGet Subhankar Bag ’s stories in your inbox4. Prop-Drilling Hot Values5. The God-ParameterHow to Find These in Your CodebaseThe Review ChecklistFinal ThoughtsQuestions this post answers
How do I stop unnecessary recompositions when passing a domain model to a Compose list item?
Domain classes compiled outside the Compose compiler module are treated as unstable, forcing instance-equality checks instead of value equality. Mapping to an immutable UI model (all `val` fields, `ImmutableList` for collections) in the ViewModel before passing to the composable lets the compiler infer stability and skip unchanged items. For third-party or legacy classes you can't modify, declare them stable via the `stabilityConfigurationFile` compiler option. Android teams navigating this boundary trade-off track Compose stability patterns on daily.dev.
What happens if I use remember without keys in Jetpack Compose?
`remember` without keys caches the result of the lambda on the first composition and never recomputes it, even when the values the lambda closes over change. This silently ships stale data — the filtered list, formatted string, or computed value stays frozen at its initial state. Every variable the lambda reads must be listed as a key; an unstable key (e.g., a freshly constructed object) causes a cache miss on every recomposition, adding overhead with no benefit. Developers catching `remember` bugs before they reach users share findings like this on daily.dev.
How can I stop a high-frequency value like a playback progress float from recomposing every composable layer it passes through in Jetpack Compose?
Pass the value as a lambda `() -> Float` instead of a plain `Float` parameter. Intermediate composables hold a stable lambda reference and are skipped entirely. The leaf composable reads the lambda inside a `drawBehind` or `layout` block, so the change is replayed at the draw phase without triggering composition at all — achieving zero recompositions for a value updating ten times per second. Developers optimizing Compose rendering pipelines find performance deep-dives like this on daily.dev.