skip to content

Compare cold Flow laziness with hot StateFlow/SharedFlow. When would you deliberately keep a flow cold, and when would you convert it to hot?

level: seniorimportance: should knowfreq 50%

answer

  1. Cold = lazy, unicast, per-collector run
  2. Hot = always exists, multicast, shared upstream
  3. stateIn -> StateFlow (latest value + initial)
  4. shareIn -> SharedFlow (events, replay)
  5. SharingStarted: Eagerly / Lazily / WhileSubscribed

basics

~20 s

Cold flows run per collector and only on collect, so they are great for on-demand, isolated work. Hot flows always exist and share one stream, good for shared state or events many parts observe at once.

solid answer

~50 s

A cold Flow is lazy and unicast: each collector triggers an independent run, ideal for request/response style operations (one DB query, one API call) where isolation and laziness matter and you don't want work until someone needs it. Hot flows (StateFlow, SharedFlow) exist independently of collectors and are multicast: one upstream feeds all subscribers. You convert cold to hot with stateIn (latest-value state, conflated, has an initial value) or shareIn (event broadcasting with configurable replay) when many consumers must observe the same single source, when you want a value to survive across collectors (e.g., UI state), or to avoid duplicating expensive upstream work. The trade: hot flows need a CoroutineScope and a SharingStarted policy (Eagerly/Lazily/WhileSubscribed) governing lifecycle, and can run with zero collectors, so you must manage their lifetime. Keep it cold when laziness and per-collector independence are assets; go hot for shared, long-lived, or multicast state.

code

kotlin · 6 lines
kotlin
// Repository exposes COLD (lazy, composable)
fun observeUser(): Flow<User> = flow { emitAll(dao.stream()) }

// ViewModel converts to HOT, lifecycle-aware UI state
val user: StateFlow<User?> = observeUser()
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)

go deeper

for a junior

Knows cold runs on collect and StateFlow always holds a current value.

for a middle

Explains unicast vs multicast and names stateIn/shareIn for conversion.

for a senior

Chooses cold vs hot per layer, picks SharingStarted, and reasons about conflation and duplicated work.

for a principal

Defines architectural conventions (cold in data layer, hot at consumption boundary), scope ownership, and lifecycle/leak policy across the app.

## Cold vs hot at a glance | Aspect | Cold `Flow` | Hot `StateFlow` / `SharedFlow` | |---|---|---| | Starts on collect? | Yes (lazy) | No — exists independently | | Collectors | Unicast: one run each | Multicast: shared upstream | | Holds value? | No | StateFlow holds latest; SharedFlow can replay N | | Needs a scope? | No | Yes (for the sharing upstream) | | Duplicated work | Per collector | Single shared upstream | ## Keep it cold when… - The operation is **on-demand and self-contained**: a single network/DB call, a query, a computation that should run only when requested. - You want **laziness** — no work until collection, and clean isolation per collector. - You're building a **composable pipeline** (`map`/`filter`/`flowOn`) returned from a repository, leaving the collection/lifecycle decision to the caller. - Each collector legitimately needs its **own fresh run** (e.g., paging that restarts). ## Convert to hot when… - **Many consumers must observe one source** without duplicating upstream work. - You need a value to **persist across collectors** and be readable synchronously — that's `StateFlow` (has `.value`, an initial value, conflated, distinct-until-changed semantics): ```kotlin val uiState: StateFlow<UiState> = repository.observe() .map { it.toUiState() } .stateIn(scope, SharingStarted.WhileSubscribed(5_000), UiState.Loading) ``` - You need to **broadcast events** (no initial value, configurable buffer/replay) — that's `SharedFlow` via `shareIn`: ```kotlin val events: SharedFlow<Event> = source .shareIn(scope, SharingStarted.Lazily, replay = 0) ``` ## SharingStarted policies - `Eagerly` — upstream starts immediately and never stops. - `Lazily` — starts on first subscriber, then stays active. - `WhileSubscribed(stopTimeoutMillis)` — starts on first subscriber, stops when the last leaves (after a timeout); the standard choice for UI to avoid leaking work when the screen is gone. ## Trade-offs - Hot flows can run with **no collectors**, so they can do work nobody observes and must be **scoped/cancelled** properly. - `StateFlow` **conflates** — fast intermediate values may be skipped; not suitable for every-event delivery (use `SharedFlow`/`Channel` for that). - Cold flows give backpressure and laziness for free but **duplicate work** under multiple collectors. ## Rule of thumb Expose **cold** flows from repositories/data sources (lazy, testable, composable); convert to **hot** at the consumption boundary (e.g., ViewModel) when you need shared, lifecycle-aware, latest-value state. Coldness is the default; hotness is a deliberate, scoped opt-in.

  • Why is WhileSubscribed(5000) common for UI StateFlow?
    It keeps the upstream alive for 5s after the last collector leaves, surviving brief config changes/rotations while still stopping work when the UI is truly gone — avoiding leaks.
  • Difference between stateIn and shareIn?
    stateIn produces a StateFlow with a single conflated latest value and a required initial value; shareIn produces a SharedFlow for broadcasting with configurable replay and no initial value.
  • Can a hot flow do work with zero collectors?
    Yes — depending on SharingStarted (Eagerly always, Lazily/WhileSubscribed conditionally), which is why scoping and cancellation matter.

Cold flow = cooking to order per customer; hot flow = a buffet that's already running and everyone shares.

saying these in an interview costs you the question

  • Claiming StateFlow is cold or that it runs per collector
  • Converting everything to hot 'to be safe', ignoring scope/lifecycle
  • Not knowing SharingStarted controls upstream lifetime
  • Forgetting StateFlow conflates and may drop intermediate values
  • Exposing hot flows from repositories without a clear scope owner

context