Explain conflation and equality-based deduplication in StateFlow. What happens if you set .value to a value equal to the current one?
answer
- Conflation = keep only latest
- Dedup via equals (==), like distinctUntilChanged
- Equal value set = no emission
- data class equals matters
- Need every value? use SharedFlow/Channel
basics
~10 sStateFlow only keeps the latest value (conflation) and ignores a new value that equals the current one, so collectors don't get notified of 'changes' to the same value.
solid answer
~40 sStateFlow is conflated: it keeps only the most recent value, so slow collectors skip intermediate values and always observe the latest. It also deduplicates using structural equality (equals/==): assigning a value that equals the current one is a no-op — no emission is sent to collectors. So updating .value to an equal value won't trigger collect blocks. This matters with data classes (correct equals) versus classes without equals (identity-based), where two distinct-but-'equal' instances may or may not be deduplicated. Implication: rapid updates may be coalesced (a collector that's busy can miss intermediate states), and equal-valued updates are silently dropped. If you need every emission delivered, StateFlow is the wrong tool — that's what SharedFlow / a Channel are for.
code
kotlin · 5 linesval s = MutableStateFlow(0)
s.value = 5
s.value = 5 // equal -> collectors NOT notified
s.value = 6 // notified
// equivalent to a flow with distinctUntilChanged()go deeper
Knows StateFlow holds the latest value and may not deliver every intermediate one.
Explains conflation plus equality dedup (== / distinctUntilChanged) and that equal sets are no-ops.
Connects dedup to equals correctness (data class vs identity) and the consequences for missed events.
Decides StateFlow vs SharedFlow/Channel based on delivery guarantees and designs state types so equality dedup behaves predictably.
## Two behaviours rolled together StateFlow combines **conflation** and **equality-based deduplication**. ### Conflation StateFlow stores only the **latest** value (a buffer/replay of 1). If a collector is slow, intermediate values it didn't get to are dropped and it simply sees the most recent one. You never get a backlog of historical values — only "the current state." ### Equality-based deduplication When you write `.value = x` (or via `update { }` / `compareAndSet`), StateFlow compares the new value to the current one using **structural equality** (`equals`, i.e. `==`). If they are equal, **nothing is emitted** — collectors are not notified. This is equivalent to wrapping a flow with `distinctUntilChanged()`. ```kotlin val s = MutableStateFlow(0) val seen = mutableListOf<Int>() val job = launch { s.collect { seen += it } } s.value = 1 // emitted s.value = 1 // EQUAL to current -> no emission s.value = 2 // emitted // seen eventually: [0, 1, 2] ``` ## Why equals matters Deduplication relies on a correct `equals`: - **data class** generates `equals`/`hashCode` from its properties, so two instances with identical fields are equal and the second is deduplicated. - A regular class without an overridden `equals` uses **referential identity**, so two distinct instances are never equal and both pass through. ```kotlin data class UiState(val loading: Boolean) val flow = MutableStateFlow(UiState(true)) flow.value = UiState(true) // equal -> dropped ``` ## Practical consequences - A busy collector can **miss intermediate states** — fine for UI state, bad for event delivery. - Setting an equal value is a silent no-op — useful to avoid redundant re-renders, surprising if you expected a tick. - If you must deliver **every** value or duplicate signals, use `SharedFlow`/`Channel` instead (out of scope here). ## Atomic updates Use `update { current -> ... }` for safe read-modify-write under concurrency; it loops on `compareAndSet` and still applies equality dedup on the result.
- How can two visually 'same' UI states still trigger an emission?If the state type lacks a value-based equals (uses identity), two distinct instances aren't equal, so the dedup check passes and both emit.
- Your collector seems to miss rapid updates — bug or expected?Expected. StateFlow conflates; a slow collector only sees the latest value, not every intermediate one.
saying these in an interview costs you the question
- Claiming StateFlow delivers every value to every collector
- Not knowing dedup uses equals/==
- Saying setting an equal value still re-emits
- Ignoring how data class equals affects dedup
- Using StateFlow for one-shot events expecting guaranteed delivery