Compose State & Hoisting Interview Questions
UI › Compose Core
Reviewing a PR, you see a composable holding its own mutableStateOf instead of taking value and onValueChange as parameters. What questions do you ask before deciding whether that's actually a problem?
What a strong answer covers: Ask who else needs to read or react to this state, whether it needs to survive only recomposition, or also configuration change and process death, and whether it represents app or business data versus purely transient UI state like whether a tooltip is expanded. Genuinely local UI-only state is fine to keep internal, but anything another composable, a ViewModel, or a test needs to observe should be hoisted at least as high as every reader and writer; the real red flag is when the internal state duplicates or silently shadows something already being passed in as a parameter.
When would you put UI state in a plain state holder class remembered in the composable, instead of either keeping it inline or pushing it up into a ViewModel?
What a strong answer covers: A plain state holder fits state that's genuinely UI-only, doesn't need to survive process death, and isn't meaningfully testable outside the UI, but is complex enough, multiple related values, derived fields, coroutine-driven animation, that spreading it across several remember calls in the composable body becomes unreadable. It avoids the lifecycle coupling and overhead of a ViewModel, which is meant for state that outlives a single composable's presence on screen, reserving the ViewModel for state that's screen-level and independently testable.
A ViewModel exposes a single StateFlow<UiState> to a composable via collectAsStateWithLifecycle. A teammate wants to split it into several separate mutableStateOf properties for 'better recomposition granularity.' What's the real trade-off, and when does splitting actually help?
What a strong answer covers: Splitting can genuinely reduce recomposition because Compose tracks reads per State object, not per field, so a single composite UiState means any field change recomposes every reader of the whole object; but splitting adds ViewModel complexity and loses the atomicity of one consistent snapshot, risking readers observing fields torn across different logical updates. The right default is to keep one state object and let derivedStateOf or the reading composable absorb finer granularity, only splitting once profiling shows a real cost on a hot path like scroll-driven UI, not preemptively.