Recomposition and skip optimization
Recomposition and Skip Optimization Deep Dive¶
Overview¶
Recomposition is selective re-execution of composables affected by state changes. Skip optimization allows runtime to avoid re-running groups whose inputs are unchanged.
Core Concepts¶
- invalidation: marking a composition scope dirty
- restart groups: compiler-defined recomposition units
- skippable groups: eligible for fast path when parameters unchanged
- stability: influences whether change detection is reliable
Runtime Internals¶
Compose compiler inserts bookkeeping parameters and group markers.
At runtime, Composer evaluates whether a group can be skipped based on:
- parameter change flags
- stability metadata
- group restartability/skippability
Unstable parameter types often reduce skip opportunities.
Composition / Recomposition Flow¶
- Initial composition records state reads.
- State mutation invalidates readers.
- Recomposer schedules work on next frame.
- Invalidated groups are revisited.
- Groups with unchanged/stable inputs are skipped.
- Apply phase commits node changes.
State Management¶
State design strongly affects recomposition behavior:
- isolate fast-changing state to narrow scopes
- avoid passing large unstable models down deep trees
- use derived state for frequently recomputed projections
- use stable item keys in lazy lists
Code Examples¶
@Composable
fun InboxScreen(state: InboxUiState) {
val unreadCount by remember(state.messages) {
derivedStateOf { state.messages.count { !it.isRead } }
}
Column {
Text("Unread: $unreadCount")
LazyColumn {
items(state.messages, key = { it.id }) { message ->
MessageRow(message = message)
}
}
}
}
@Composable
fun SearchResultList(
query: String,
results: List<ResultUi>
) {
// Keep expensive filtering scoped and memoized to inputs.
val visible by remember(query, results) {
derivedStateOf { results.filter { it.title.contains(query, ignoreCase = true) } }
}
LazyColumn { items(visible, key = { it.id }) { ResultRow(it) } }
}
Common Interview Questions¶
- Q: What exactly triggers recomposition? A: Explain runtime behavior: what invalidates state, how recomposition is scoped, where side effects live, and how to verify frame stability with profiler traces.
- Q: Why does unstable input type hurt performance? A: Answer from runtime mechanics: state ownership, recomposition triggers, effect lifecycle, and frame-time impact measured with tooling.
- Q: Is recomposition the same as redraw? A: Explain runtime behavior: what invalidates state, how recomposition is scoped, where side effects live, and how to verify frame stability with profiler traces.
- Q: How do you diagnose "too much recomposition"? A: Explain runtime behavior: what invalidates state, how recomposition is scoped, where side effects live, and how to verify frame stability with profiler traces.
Production Considerations¶
- Profile before micro-optimizing.
- Keep composables focused and parameter surfaces minimal.
- Watch for frequently recreated lambdas/objects in hot paths.
- Prefer immutable UI models with stable identity fields.
Performance Insights¶
- Not all recompositions are bad; unnecessary broad recompositions are.
- High skip rates in hot areas usually improve frame consistency.
- Large list screens are sensitive to key and stability mistakes.
Senior-Level Insights¶
Top-tier answers discuss tradeoffs:
- readability first, then targeted optimization
- use compiler metrics/tracing for evidence-driven tuning
- architecture decisions upstream determine runtime cost downstream