Jetpack Compose Best Practices
Keep your UI fast, smooth, and recomposition-free! Learn the official optimization patterns every Android dev should know.
What you’ll learn
- Optimize recompositions with remember & derivedStateOf
- Use stable keys for lazy lists
- Defer state reads & move heavy work outside composition
- Avoid backwards writes & redundant recompositions
- Hoist and manage state efficiently
Cache Expensive Work
Use remember to store heavy computations and avoid repeating them on every recomposition.
val sorted = remember(contacts, comparator) {
contacts.sortedWith(comparator)
}
LazyColumn { items(sorted) { ContactItem(it) } }
💡 Tip: Run once, reuse often.
Stable Keys in Lazy Lists
Provide stable key values to prevent unnecessary recompositions when list items move.
LazyColumn {
items(notes, key = { it.id }) { note ->
NoteRow(note)
}
}
💡 Tip: Stable keys = smoother scrolling.
Limit Recomposition
Use derivedStateOf to recompose only when truly needed.
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
AnimatedVisibility(showButton) { ScrollToTopButton() }
💡 Tip: Compose smart, not often.
Defer State Reads
Read fast-changing state only where it’s used, not at the parent level.
Title(snack) { scroll.value }
@Composable
fun Title(snack: Snack, scrollProvider: () -> Int) {
val offset = with(LocalDensity.current) { scrollProvider().toDp() }
Column(Modifier.offset(y = offset)) { Text(snack.name) }
}
💡 Tip: Read only where needed.
Use Lambda Modifiers
Lambda-based modifiers like offset {} or drawBehind {} skip the composition phase and improve frame time.
Box(
Modifier.drawBehind {
drawRect(color)
}
)
💡 Tip: Moves state reads to layout/draw phase.
Avoid Backwards Writes
Never modify state after reading it in the same composition - it causes infinite recompositions.
var count by remember { mutableIntStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
// ❌ count++ // Never write after a read!
💡 Tip: Always update state in event callbacks.
Hoist State Up
Keep state outside child composables so they remain stateless and reusable.
var name by rememberSaveable { mutableStateOf("") }
NameInputField(name, { name = it })
💡 Tip: Children should display, not own, state.
Avoid Heavy Work in Composition
Don’t call I/O, network, or database operations in composables. Use a ViewModel and collect with collectAsState().
val uiState by viewModel.state.collectAsState()
Text(uiState.title)
💡 Tip: Compose = UI, not business logic.
Final Checklist
- ✅ Cache heavy work with
remember - ✅ Add stable keys for Lazy lists
- ✅ Use
derivedStateOfwisely - ✅ Defer reads using lambda modifiers
- ✅ Avoid backwards writes
- ✅ Hoist state up
- ✅ Offload logic to ViewModel
Thank You - Follow Boltuix
Daily Jetpack Compose insights - layouts, shadows, adaptive UIs & performance tips.


#Android #JetpackCompose #Kotlin #ComposePerformance #AndroidDevelopers










