StateFlow & SharedFlow Quiz

KOTLIN › Flow

A ViewModel exposes screen state. Which flow type is the idiomatic choice and why?

Answer: StateFlow, because it keeps the latest value, has an initial one, and conflates

UI state is a single always-available current value, which is exactly StateFlow's contract: required initial value, holds latest, conflated. A cold flow or single-consumer Channel does not model shared observable state.

Why is StateFlow a poor choice for emitting a one-time navigation event?

Answer: StateFlow replays its latest value to new collectors, so the event refires

A new collector (e.g. after a configuration change) immediately receives StateFlow's current value, so a navigation 'event' stored as state would be re-triggered. SharedFlow with replay = 0 avoids this.

What is the default onBufferOverflow behavior of MutableSharedFlow when emit() is called and the buffer is full?

Answer: It suspends the emitting coroutine until buffer space opens

The default policy is BufferOverflow.SUSPEND, so the suspending emit() call waits for buffer space. tryEmit() instead returns false rather than suspending.

You set MutableStateFlow.value 100 times in a tight loop while a single collector is slow. What does that collector observe?

Answer: It may skip intermediates, but it will eventually get the latest value.

StateFlow conflates: a slow collector is not guaranteed every intermediate value, only that it converges on the most recent value. There is no buffer to overflow and no waiting for completion.

A MutableStateFlow holds a UiState data class. You assign a new instance that is equal() to the current value. What happens?

Answer: No new emission, because StateFlow emits only when the value is unequal

StateFlow compares with equals (like a built-in distinctUntilChanged), so assigning an equal value produces no emission. With a data class, structurally equal content suppresses the update even though it is a new object reference.

What is the idiomatic way to expose a ViewModel's writable StateFlow so external callers can read but not emit to it?

Answer: Keep a private MutableStateFlow and expose it with .asStateFlow()

asStateFlow() returns a read-only StateFlow view over the private MutableStateFlow, preventing outside code from calling value/emit while still observing updates. A bare cast would still allow downcasting back to the mutable type.

Two coroutines run this concurrently and updates are being lost. What is the correct fix?

Answer: Use state.update { it.copy(count = it.count + 1) } for atomic retries

Read-modify-write on .value is not atomic, so concurrent updates race. MutableStateFlow.update runs the lambda inside a compareAndSet retry loop, guaranteeing each update is applied without losing the other's change.

A MutableSharedFlow is created with replay = 2. After five values have been emitted, a new collector subscribes. What does it receive first?

Answer: The last two cached values, then any later emissions

replay defines how many of the most recent emissions are cached and re-delivered to new subscribers. With replay = 2 a late collector immediately gets the last two values, then continues with new emissions.

You want to multicast an expensive cold flow to several collectors as one-off events with no natural initial value. Which operator fits?

Answer: shareIn

shareIn converts a cold flow into a hot SharedFlow shared across collectors and, with replay = 0, models events. stateIn requires an initial value and models state, while conflate/buffer are per-collector operators that do not multicast.

What type is MutableSharedFlow.subscriptionCount?

Answer: A StateFlow<Int> you can observe as collectors come and go

Being observable is the point: it lets upstream work start and stop with the subscriber count, which is exactly how SharingStarted.WhileSubscribed is implemented.

A MutableSharedFlow with replay = 0 emits while nothing is collecting. What happens to the value?

Answer: It is dropped silently

With no replay cache and no subscriber there is nowhere to hold it. Raising replay to 1 trades this for re-delivery after a rotation.

Why does a Channel with receiveAsFlow() suit one-off events better than a SharedFlow?

Answer: It buffers when nobody listens and delivers each element exactly once

That combination avoids both SharedFlow failure modes. The cost is a single collector: two would split the events between them rather than each receiving all.

What is the main limitation of using a Channel as an event bus?

Answer: It supports one collector: two would split the events between them

A channel delivers each element to exactly one receiver, which is the property that makes it right for events and wrong whenever several independent listeners each need to see them all.

Why is StateFlow conflation specifically a problem for events?

Answer: The same event emitted twice in a row is delivered only once

Equality with the current value suppresses the emission, so the second identical snackbar never appears. For UI state that suppression is desirable, which is the whole distinction.

Which advantage does StateFlow have over LiveData?

Answer: It is plain Kotlin, so a ViewModel using it is JVM-testable with no Android test rules

No Android dependency means no Robolectric and no InstantTaskExecutorRule, plus the whole operator library. Lifecycle awareness is the thing it gives up.

Which advantage does LiveData retain over StateFlow?

Answer: observe(owner) is lifecycle-aware without any extra work

That is the trade the newer API made: more power, less safety by default. Both conflate, and StateFlow is the one with the guaranteed value and the operators.

What does resetReplayCache() accomplish?

Answer: Clears replayed values so a new subscriber receives none, without emitting anything

It is the workaround for a replay-1 event being re-delivered after a rotation. StateFlow has no equivalent, because it must always have a current value.

Back to StateFlow & SharedFlow