Flow & Channels
A Flow is a cold asynchronous stream: a description of where values come from, not the values themselves. The flow { ... } builder does nothing until a terminal collect arrives; every new collector re-runs the producer from scratch, as its own independent execution. That sets Flow apart from hot streams — StateFlow and SharedFlow — which live and emit whether or not anyone is listening, and from Channel, a hot queue for handing values between coroutines where each element reaches exactly one receiver. Backpressure here is not extra machinery but a direct consequence of emit and collect being suspend functions: a slow collector simply suspends the producer.
The dividing line of the topic is cold versus hot, and nearly every trap lives on it. A cold Flow caches nothing and shares no run between collectors (two collectors, two runs). Intermediate operators (map, filter, catch) are lazy: they only build the chain and execute inside the collector's coroutine, which is why flowOn changes the context for upstream only — everything declared above it — and cannot move collect off the calling dispatcher. A hot StateFlow always holds one current value, conflates fast emissions, and drops a value equal to the previous one (equals) — that is state, not events; for one-shot events you use a SharedFlow, which deduplicates nothing. And a Channel is for when a value must reach one consumer, not all of them at once. The layer-by-layer breakdown is below.
Topic map
- Cold Flow — why the producer does not start before
collectand re-runs from scratch for every collector, and how that differs from asuspendfunction's single value. - Flow operators — intermediate operators build a lazy chain, terminal operators start it, and
catchsees only what is upstream. - flowOn and context preservation — why
flowOnaffects upstream only, never touches the collector, and replaces the forbiddenwithContextinsideflow { }. - Backpressure — suspending
emitas the built-in mechanism, and howbuffer,conflateandcollectLatestchange it. - StateFlow — a hot single-value holder, conflation, dropping duplicates via
equals, and turning a cold flow into state withstateIn. - SharedFlow — a configurable hot broadcast with
replayand a buffer, whyStateFlowis a special case of it, and why it is the tool for events. - Channel — a hot suspending queue, one element to one receiver, and the duty to close or consume it or it leaks.
- Flow versus Channel — a cold pipeline versus a hot handoff, the "Flow first" rule, and the role of
callbackFlowas a bridge.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
Thinking a cold Flow caches its values and replays them to later collectors | Each collector re-runs the producer — two collectors are two independent runs |
Expecting a cold Flow to start producing before the first collect | Nothing runs before a terminal operator: no collector means no work and no side effects |
| Believing an operator chain does work before a terminal operator runs | map/filter only return a new Flow; the work happens on collect, once per collection |
| Thinking each operator gets its own coroutine or thread | The chain runs sequentially in the collector's single coroutine, with no extra threads |
Expecting catch to handle an exception from the collect block | catch sees upstream only; a collector error is downstream and propagates to the caller |
Believing flowOn moves collect off the calling dispatcher | flowOn changes the context for upstream only; the collector always runs in the calling coroutine |
Placing flowOn at the end of the chain to "cover" the collector | flowOn acts upward from its call site; operators and collect below it are untouched |
Calling withContext(IO) { emit(x) } inside flow { } | A context-preservation violation — a runtime "Flow invariant is violated" error; use .flowOn(IO) |
Assuming a Flow buffers by default | By default collection is sequential and emit suspends on a slow collector — that is backpressure |
Confusing conflate with collectLatest | conflate drops intermediate values; collectLatest cancels and restarts the collector body |
Using StateFlow for one-shot events | It conflates and drops a value equal to the current one by equals — two identical events become one |
Expecting a StateFlow to deliver every emission of a fast producer | It is conflated: a slow collector sees only the latest value, the intermediate ones are gone |
Thinking a SharedFlow needs an initial value like StateFlow | A SharedFlow holds no value; replay = 0 means a late subscriber misses past events |
Expecting a Channel to broadcast a copy of each element to every receiver | Each element reaches exactly one receiver — that is fan-out of work, not a broadcast |
Thinking a Channel is cold and starts a fresh producer per receiver | A Channel is hot: an uncollected one keeps its producer and buffer alive — a leak |
Exposing a hot Channel where a cold Flow would do | You lose the cold stream's structural safety; UI state needs a StateFlow, not a raw Channel |
Interview relevance
Flow is the section where a candidate is tested on their execution model, not on operator names. The key question of the topic is "is it cold or hot, and what does that physically mean": a cold Flow does nothing until collect and re-runs per collector; a hot StateFlow/SharedFlow lives on its own. From that single distinction follow the laziness of operators, why flowOn never touches the collector, and why StateFlow loses a duplicate. The interviewer usually walks from "how is a Flow better than a suspend function" to "why does the UI stall despite flowOn" to "why does the second identical toast not arrive" — three traps on one and the same model.
Typical checks:
- What "cold" means: when the producer starts and what two collectors of one
Flowsee. - When intermediate operators actually run, and which operators are terminal.
- What
catchactually catches, and why it misses an error thrown insidecollect. - The direction of
flowOn: why it affects upstream only and cannot movecollect. - How
buffer,conflateandcollectLatestchange behaviour under a fast producer. - The difference between
StateFlowandSharedFlow, and whyStateFlowis the wrong carrier for events. - How a
Channeldelivers elements to two receivers, and when to reach for aChannelover aFlow.
Common wrong answer: "a cold Flow computes its values once and hands them to every collector, like a cache." From there the candidate concludes that two collectors share one run, that SharedFlow is the one that re-runs the producer, and that flowOn at the end of the chain will cover collect too. In reality it is the other way round: a cold Flow caches nothing and re-runs the producer for every collector; a SharedFlow is hot and shares one emission stream; and flowOn changes the context only for what is declared above it, while the collector always stays on the calling dispatcher.