Compose Side Effects Quiz
UI › Compose Core
Why should composable functions avoid performing side effects directly in their body?
- Composables run on a background thread, so blocking side effects are banned
- They can recompose often, run out of order, or be dropped unpredictably
- The Compose compiler removes non-UI statements from every composable body
- Any side effect in a composable will always trigger an infinite recomposition
Answer: They can recompose often, run out of order, or be dropped unpredictably
Composables can recompose frequently, execute in any order, and be abandoned, so directly mutating external state runs unpredictably; effect APIs provide a lifecycle-aware place for it.
What happens to a LaunchedEffect's coroutine when one of its keys changes during recomposition?
- The existing coroutine keeps running and a second one is added
- Nothing changes until the composable leaves composition
- The running coroutine is cancelled and a new one is launched
- The key change is ignored because keys only matter on first composition
Answer: The running coroutine is cancelled and a new one is launched
A key change cancels the currently running coroutine and launches a fresh one with the new keys; stable keys keep the same coroutine alive.
You need to show a snackbar when a Button is clicked. Which API launches the coroutine correctly?
- Call rememberCoroutineScope(), then scope.launch { } in onClick
- Put LaunchedEffect inside onClick to start the snackbar coroutine
- Wrap the showSnackbar call in SideEffect when the button is clicked
- Call GlobalScope.launch { } directly from onClick to show it
Answer: Call rememberCoroutineScope(), then scope.launch { } in onClick
onClick is not a composable scope, so LaunchedEffect cannot be used there; rememberCoroutineScope gives a composition-aware scope, while GlobalScope is not lifecycle-bound.
An effect uses LaunchedEffect(Unit) with a delay, then invokes an onTimeout callback that may change across recompositions. How do you call the latest callback without restarting the timer?
- Add onTimeout as a key to LaunchedEffect, so the timer restarts
- Move onTimeout into a SideEffect block to run it after each recomposition
- Wrap onTimeout in rememberUpdatedState and call its value
- Recreate the effect inside DisposableEffect so the delay stays tied to cleanup
Answer: Wrap onTimeout in rememberUpdatedState and call its value
rememberUpdatedState keeps a reference that is always current, so the long-lived effect calls the latest callback without restarting; adding it as a key would cancel and restart the delay.
Which statement about DisposableEffect is correct?
- It runs its block after each successful recomposition, like SideEffect
- Its block must end with onDispose for cleanup on exit or key change
- It returns a Compose State object that merges equal values together
- It can only be used from inside an event handler, not composition
Answer: Its block must end with onDispose for cleanup on exit or key change
DisposableEffect is for effects needing cleanup; the mandatory onDispose block runs when the effect leaves composition or a key changes. Running after every recomposition describes SideEffect.
You want to send an analytics event the first time a LazyColumn is scrolled past its first item, using Flow operators. Which approach fits best?
- Read firstVisibleItemIndex directly in the composable body and call analytics inline
- Use a SideEffect block to watch the scroll index and send the event each time
- Expose the scroll index as State via produceState and watch for the change
- Collect snapshotFlow { listState.firstVisibleItemIndex } in a LaunchedEffect
Answer: Collect snapshotFlow { listState.firstVisibleItemIndex } in a LaunchedEffect
snapshotFlow turns the State read into a Flow you can transform with operators, collected from a coroutine; reading directly in the body would fire on every recomposition without Flow semantics.
What is the primary purpose of produceState?
- To turn a Flow or suspend load into Compose State<T> for UI use
- To publish Compose state back to non-Compose objects after recomposition
- To ensure an effect runs once and never gets cancelled by recomposition
- To cache an expensive calculation without involving any mutable state
Answer: To turn a Flow or suspend load into Compose State<T> for UI use
produceState launches a composition-scoped coroutine and exposes the result as State<T>, bridging external sources into Compose; publishing to non-Compose code is SideEffect's job.
You need to keep a non-Compose analytics SDK's current-user property in sync with Compose state. Which effect API is designed for this, and when does it run?
- DisposableEffect, running its block once when entering composition
- produceState, running a coroutine scoped to composition
- SideEffect, running after every successful recomposition
- rememberCoroutineScope, running inside an event handler
Answer: SideEffect, running after every successful recomposition
SideEffect runs after each successful recomposition and is meant for publishing Compose-managed state to non-Compose objects; running only after success avoids acting on a recomposition that gets discarded.
What is the fundamental distinction between using rememberCoroutineScope and LaunchedEffect to start a coroutine?
- LaunchedEffect is composition-keyed; rememberCoroutineScope is for callback launches
- rememberCoroutineScope always uses a background thread, while LaunchedEffect stays on Main
- Only LaunchedEffect cancels its coroutine when the call site leaves composition and disappears
- rememberCoroutineScope automatically restarts its coroutine whenever any state in the UI changes
Answer: LaunchedEffect is composition-keyed; rememberCoroutineScope is for callback launches
LaunchedEffect ties a coroutine's lifecycle to composition and its keys (auto cancel and restart), whereas rememberCoroutineScope hands you a composition-scoped scope to launch from event handlers; both cancel when leaving composition.
Inside produceState, how do you clean up a listener you registered on a non-suspending, callback-based source?
- Return an onDispose { } block at the very end, exactly as in DisposableEffect
- Call awaitDispose { } at the end; it suspends until disposal, then runs cleanup
- Override a dispose() method on the State object that produceState returns
- Nothing is needed since produceState cannot register any listeners
Answer: Call awaitDispose { } at the end; it suspends until disposal, then runs cleanup
produceState runs a coroutine, so for a callback-based source you register the listener and then call awaitDispose, which suspends until disposal and runs its lambda; onDispose belongs to DisposableEffect.
Which statement best describes snapshotFlow?
- A hot flow that replays the current State value to new collectors
- It pushes State changes to non-Compose code without a collector
- It works only outside composition and not inside a LaunchedEffect
- A cold Flow that runs in a snapshot and conflates equal results
Answer: A cold Flow that runs in a snapshot and conflates equal results
snapshotFlow is cold: on collection it runs its block, records which State objects were read, and re-emits when any of them changes, conflating equal consecutive results. It is typically collected inside a LaunchedEffect.
You collect a Flow from a ViewModel inside a LaunchedEffect to drive the UI. What is the recommended way to stop that collection while the app is in the background?
- Use repeatOnLifecycle(STARTED) or collectAsStateWithLifecycle
- Add the LifecycleOwner as a key so LaunchedEffect restarts on change
- Move the collection into a SideEffect block so it pauses in background
- Launch in GlobalScope so the system suspends the coroutine in background
Answer: Use repeatOnLifecycle(STARTED) or collectAsStateWithLifecycle
A plain LaunchedEffect keeps collecting as long as the composable stays in composition, even in the background; repeatOnLifecycle (or collectAsStateWithLifecycle) ties collection to the STARTED state so it stops and restarts with the lifecycle.
A DisposableEffect has a key that changes during recomposition. In what order do its parts run?
- The effect block runs again first, then the old onDispose runs afterward
- Only onDispose runs now; the block waits for another key change before re-running
- The old onDispose runs first, then the effect block runs again with the new key
- Nothing runs, because DisposableEffect stops reacting to key changes after first composition
Answer: The old onDispose runs first, then the effect block runs again with the new key
On a key change DisposableEffect first runs the old onDispose to clean up the previous registration, then executes the block again with the new keys.
What happens to the coroutine started by a LaunchedEffect when its call site permanently leaves composition?
- It keeps running to completion, detached from the UI.
- It is cancelled, like a key change but with no restart.
- It is paused and resumes only when the composable returns.
- It is reparented to GlobalScope so it can finish safely.
Answer: It is cancelled, like a key change but with no restart.
LaunchedEffect scopes its coroutine to composition, so leaving composition cancels it with no restart, mirroring the cancellation half of the key-change behavior.