If you’re starting with Jetpack Compose, you’ve probably heard the word Recomposition.
Sounds scary? Don’t worry. In this blog, we’ll break it down step by step — simple enough that even a beginner can master it.
By the end, you’ll know how to make your Jetpack Compose apps smooth, fast, and almost recomposition-free.
What is Recomposition in Jetpack Compose?
In Jetpack Compose, the UI is reactive. That means when the state (data) changes, Compose redraws the affected parts of the UI.
This redraw process is called Recomposition.
👉 Think of it like repainting only the wall where you added a new frame, not the entire house.
Good news: Recomposition is not bad.
Better news: You can control and reduce unnecessary recompositions.
Why Reduce Recomposition?
- ✅ Faster UI performance
- ✅ Less battery usage
- ✅ Smooth scrolling in
LazyColumnand lists - ✅ Professional-level code quality
🛠 Best Practices to Minimize Recomposition in Jetpack Compose
1. Use Stable and Immutable Data Classes
import androidx.compose.runtime.Immutable
@Immutable
data class UserUi(
val id: Int,
val name: String,
val avatarUrl: String
)
- Immutable = never changes internally → Compose can safely skip recomposition.
- Annotate with
@Immutableor@Stablewhen possible.
2. Avoid Creating New Objects in Composables
❌ Bad: Creates a new object every recomposition
UserCard(user = UserUi(1, "Alex", "url"))
✅ Good: Remember or hoist the object
val user = remember { UserUi(1, "Alex", "url") }
UserCard(user)
3. Hoist State to the Top (State Hoisting)
Keep state at the highest level and pass only what children need.
@Composable
fun UserScreen(viewModel: UserViewModel) {
val user by viewModel.user.collectAsState()
UserCard(name = user.name, avatar = user.avatarUrl)
}
👉 Only the UserCard updates when user data changes.
4. Use remember and derivedStateOf Wisely
remember {}→ Cache expensive objects.derivedStateOf {}→ Recompute only when dependencies change.
@Composable
fun SearchResults(query: String, list: List<String>) {
val filtered by remember(query, list) {
derivedStateOf { list.filter { it.contains(query) } }
}
ResultsList(filtered)
}
5. Stable Lambdas with rememberUpdatedState
Passing lambdas directly can trigger recomposition.
Use stable references:
@Composable
fun ActionButton(onClick: () -> Unit) {
Button(onClick = onClick) { Text("Click Me") }
}
ActionButton(onClick = viewModel::onSubmit) // Stable reference
If your lambda captures changing values:
val latestAction by rememberUpdatedState(newAction)
Button(onClick = { latestAction() }) { Text("Safe Click") }
6. Optimize Lists with Keys
When working with LazyColumn or LazyRow:
LazyColumn {
items(users, key = { it.id }) { user ->
UserCard(user)
}
}
✅ Stable keys = only changed rows recompose, not the whole list.
7. Cache Heavy Objects in Modifiers
val brush = remember { Brush.linearGradient(colors) }
Box(Modifier.background(brush))
Saves Compose from recreating expensive objects every frame.
Tools to Detect Recomposition
- Layout Inspector → Composition tab → See recomposition counts.
- Perfetto + Compose Tracing → Timeline of recompositions.
- Benchmarking → Test smoothness & performance.
Quick Checklist (Stick on Your Desk!)
- Use
@Immutableor stable data - Hoist state to the parent
- Cache with
remember - Use
derivedStateOffor computed values - Stable lambdas (
::functionorrememberUpdatedState) - Provide stable keys in Lazy lists
Final Thoughts
You cannot remove recomposition completely — it’s the heart of Jetpack Compose.
But you can make your UI almost recomposition-free by writing stable, state-friendly, and well-structured code.
If you follow these tips, your Jetpack Compose apps will feel buttery smooth, professional, and ready for production.

