Compose State & Hoisting Quiz

UI › Compose Core

A composable has var name = mutableStateOf("") (no remember) and a TextField bound to it. The user types but nothing appears. What is the primary problem?

Answer: A new MutableState is created each recomposition without remember, losing it

On each recomposition the un-remembered mutableStateOf re-initializes to "", so the update is lost. Wrapping it in remember keeps the same state instance across recompositions.

Which statement about remember vs rememberSaveable is correct?

Answer: rememberSaveable survives rotation/config changes; remember is lost

rememberSaveable uses the saved instance state mechanism to survive activity/config recreation; plain remember is lost. Custom non-Bundleable types still need a Saver or @Parcelize.

You are converting a stateful composable into a stateless one. Which signature best expresses correct state hoisting?

Answer: fun Field(value: String, onValueChange: (String) -> Unit)

Hoisting passes the immutable value down and an event lambda up (value + onValueChange), keeping the composable stateless, reusable, and giving the caller a single source of truth.

In a chat screen, showScrollToTop should be true only when the list is scrolled past the first item. The scroll offset updates on every frame while scrolling. What is the most efficient way to drive the button's visibility?

Answer: Wrap the boolean in derivedStateOf { listState.firstVisibleItemIndex > 0 }

derivedStateOf recomputes from the rapidly changing input but only notifies readers when the derived boolean actually flips, avoiding recomposition on every frame of scrolling.

Following the official rules, where should a piece of state be hoisted?

Answer: To at least the lowest common parent that reads it and highest that changes it

The guidance is to hoist to at least the lowest common parent that reads the state and at least the highest level that changes it, and to hoist states that change on the same events together.

Which approach correctly uses a non-observable collection as Compose state?

Answer: Use mutableStateListOf() or an immutable List in MutableState

Plain ArrayList/mutableListOf are not observable, so mutations do not schedule recomposition. mutableStateListOf, or replacing an immutable List held in MutableState, is observable.

What is the recommended practice for providing a ViewModel's state to a deep child composable?

Answer: Obtain the ViewModel only at screen level, passing state and lambdas down

ViewModels should be obtained at screen level only; children receive hoisted immutable state and callbacks, keeping them stateless, reusable, and testable rather than coupled to the ViewModel.

To avoid writing .value everywhere you declare var text by remember { mutableStateOf("") } and access text directly. What actually makes this delegated property work while staying observable?

Answer: getValue/setValue operator extensions on (Mutable)State back the delegate

Compose ships getValue/setValue operator extensions for State and MutableState; importing them lets the by delegate read and write through .value while the value remains observable State.

You write remember { expensiveParse(json) }, but when the json parameter changes the composable keeps showing the previously parsed result. What is the correct fix?

Answer: Pass json as a key: remember(json) { expensiveParse(json) }

remember caches its result across recompositions and only recomputes when one of its key arguments changes by equals; passing json as a key invalidates the cache whenever json changes.

rememberSaveable { mutableStateOf(myFilter) }, where MyFilter is an ordinary data class, throws at runtime. Why, and what is the cleanest fix?

Answer: Only Bundle-storable types work; make MyFilter Parcelable or use a Saver

rememberSaveable persists through saved instance state, which is Bundle-backed, so an arbitrary class fails unless it is Bundleable; making it @Parcelize or passing a Saver resolves it.

Inside a LaunchedEffect you want to fire a suspend analytics call only when listState.firstVisibleItemIndex actually changes. Which API best bridges Compose State to a Flow you can collect there?

Answer: snapshotFlow { listState.firstVisibleItemIndex } in the effect

snapshotFlow turns reads of Compose State into a cold Flow that emits a new value whenever the observed snapshot state changes, which is exactly how you drive suspend work from inside an effect.

Which of these is the WEAKEST candidate for derivedStateOf (least likely to provide any benefit)?

Answer: fullName produced by concatenating firstName and lastName, both edited directly in TextFields

derivedStateOf only pays off when its inputs change far more often than the computed result; concatenating two names changes the output on every input change, so it adds overhead without reducing recompositions.

With var count by remember { mutableStateOf(0) }, you execute count = 0 while it already holds 0. What happens?

Answer: No recomposition is scheduled: equal values are treated as unchanged.

mutableStateOf defaults to structuralEqualityPolicy, so writing a value that equals (by equals) the current one is a no-op that does not invalidate readers; a policy like neverEqualPolicy would instead force notification.

A reusable stateless SearchField(value, onValueChange) also keeps var internal by remember { mutableStateOf(value) } and displays internal instead of value. Users report it ignores programmatic updates pushed from the parent. Which principle is violated?

Answer: Single source of truth; internal state can diverge from the hoisted value

A hoisted, controlled composable must render the passed-in value directly; duplicating it in internal remember state creates a second source of truth that ignores parent updates and breaks unidirectional data flow.

Back to Compose State & Hoisting