skip to content

Explain conflation and equality-based deduplication in StateFlow. What happens if you set .value to a value equal to the current one?

level: middleimportance: must knowfreq 70%

answer

  1. Conflation = keep only latest
  2. Dedup via equals (==), like distinctUntilChanged
  3. Equal value set = no emission
  4. data class equals matters
  5. Need every value? use SharedFlow/Channel

basics

~10 s

StateFlow 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 s

StateFlow 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 lines
kotlin
val 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

for a junior

Knows StateFlow holds the latest value and may not deliver every intermediate one.

for a middle

Explains conflation plus equality dedup (== / distinctUntilChanged) and that equal sets are no-ops.

for a senior

Connects dedup to equals correctness (data class vs identity) and the consequences for missed events.

for a principal

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

context