skip to content

When a new collector subscribes to a StateFlow, what does it receive, and how does replay-of-1 interact with conflation for a slow collector?

level: seniorimportance: should knowfreq 50%

answer

  1. replay = 1: new collector gets current value first
  2. Slow collector -> conflated to latest
  3. collect never completes; must be scoped
  4. .value readable without collecting
  5. Need every value? not StateFlow

basics

~10 s

A new collector immediately gets the current value, then future changes. If the collector is slow, it can skip intermediate values and only sees the latest one each time it's ready.

solid answer

~40 s

StateFlow has replay = 1: any new collector first receives the current `.value`, then each subsequent update. Because StateFlow is conflated, a slow collector never queues a backlog — while it's busy, newer updates overwrite the pending one, so when it resumes it observes only the most recent value. Equal consecutive values are deduplicated, so a slow collector may observe far fewer values than were assigned. This is by design for state: you only ever care about the latest. StateFlow.collect never completes on its own (the flow is always active), so you typically collect within a scope/lifecycle that cancels it. To observe every value without conflation you'd need a different primitive (SharedFlow/Channel). The current value is also always readable synchronously via .value, independent of collection.

code

kotlin · 4 lines
kotlin
val s = MutableStateFlow(0)
launch { s.collect { v -> delay(100); println(v) } }
repeat(5) { s.value = it }
// prints 0, then 4 — intermediates 1,2,3 conflated away

go deeper

for a junior

Knows a new collector gets the current value, then updates.

for a middle

Explains replay-of-1 plus conflation so slow collectors skip intermediates.

for a senior

Reasons about never-completing collection, scoping/lifecycle, and why intermediate states are legitimately dropped.

for a principal

Weighs StateFlow's conflated latest-value semantics against event-stream needs and designs collection lifecycles and backpressure strategy accordingly.

## What a new collector sees StateFlow behaves like a flow with **`replay = 1`**: the moment you start collecting, the collector is delivered the **current value**, and thereafter every change. ```kotlin val s = MutableStateFlow("A") s.value = "B" launch { s.collect { println(it) } } // prints B first (current), then future values ``` ## Conflation for a slow collector StateFlow keeps only the latest value. If your collector's lambda is slow (e.g. doing heavy work or suspending): - While it's busy, multiple `.value` writes can occur. - Those are **conflated** — only the most recent is retained. - When the collector becomes ready, it receives just that latest value, skipping the intermediates. Combined with equality dedup, a collector may observe only a fraction of the assignments made. ```kotlin val s = MutableStateFlow(0) launch { s.collect { v -> delay(100) // slow consumer println(v) } } repeat(5) { s.value = it } // fast producer 0..4 // collector likely prints 0 then 4 (intermediates conflated away) ``` ## Never completes `StateFlow.collect` is an endless suspending call — the flow is always active and has no terminal event. You must scope it (e.g. `viewModelScope`, or `repeatOnLifecycle` on Android) so cancellation stops collection. It also means terminal operators like `toList()` would hang. ## Synchronous read still available Independently of collection you can always read `.value` for the latest state — useful for one-off reads (e.g. restoring UI after a configuration change) without subscribing. ## When this is wrong If you need to process **every** emitted value (events, analytics, animations), conflation loses data — reach for `SharedFlow`/`Channel`. StateFlow is purpose-built for "the latest state," not an event stream.

  • Why would s.toList() on a StateFlow hang?
    StateFlow never completes — it's always active with no terminal event — so a terminal collector like toList() waits forever unless cancelled.
  • How do you collect a StateFlow safely on an Android UI?
    Collect inside a lifecycle-aware scope (e.g. repeatOnLifecycle(STARTED) within lifecycleScope) so it cancels when the UI stops.

Like a scoreboard: walk up anytime and you see the current score; if you blink during a flurry of points you just see the new total, not each point.

saying these in an interview costs you the question

  • Saying a new collector misses the current value
  • Expecting StateFlow.collect to complete on its own
  • Assuming every assigned value reaches every collector
  • Using toList()/first-without-scope and expecting termination
  • Treating StateFlow as a reliable event bus

context