What is the core difference between a Channel and a Flow in Kotlin coroutines, and what does it mean to call a Channel 'hot' and a Flow 'cold'?
answer
- Flow = cold = re-runs per collect
- Channel = hot = lives without a receiver
- Channel element -> exactly one receiver
- receiveAsFlow / consumeAsFlow bridge
- Inter-coroutine handoff = Channel
basics
~20 sA Channel is like a queue that passes items between coroutines; each item is taken by one receiver. A Flow is a recipe that re-runs from the start for every collector. Channel is hot (always live); Flow is cold (starts when collected).
solid answer
~40 sA Flow is cold: nothing runs until a terminal operator like collect starts it, and each collect call re-executes the producer block from scratch. It carries no buffered state and a value goes to whoever is collecting. A Channel is hot: it exists and accepts send() calls independent of any receiver, buffering elements according to its capacity. Each element sent to a plain Channel is consumed by exactly one receiver (single-consumption / fan-out semantics), so multiple receivers split the stream rather than each getting a full copy. So you reach for Channel for inter-coroutine communication (one coroutine produces, another consumes), and Flow for declarative, replayable, multi-collector streams. receiveAsFlow / consumeAsFlow bridge the two.
code
kotlin · 7 linesval flow = flow { emit(1); emit(2) } // cold: each collect re-emits 1,2
flow.collect(::println) // 1 2
flow.collect(::println) // 1 2 again
val channel = Channel<Int>() // hot: lives independently
launch { channel.send(1); channel.send(2); channel.close() }
for (x in channel) println(x) // each value received oncego deeper
States the hot/cold distinction and that a Channel passes items between coroutines while a Flow is a replayable recipe.
Adds single-consumption fan-out for Channel vs full-stream-per-collector for Flow and names receiveAsFlow/consumeAsFlow.
Explains when to choose each, the lazy per-collector execution of Flow, and why SharedFlow/StateFlow exist for hot multicast.
Frames it as a design choice (push handoff vs declarative pull), discusses backpressure/buffering implications and API surface (expose Flow, produce via Channel).
## The two abstractions **Flow** (`kotlinx.coroutines.flow.Flow`) is a *cold asynchronous stream*. "Cold" means the code that emits values does **nothing** until a **terminal operator** (most commonly `collect`) is called. Each separate `collect` runs the producer block **again from the beginning**, so two collectors each get their own independent run of all the values. **Channel** (`kotlinx.coroutines.channels.Channel`) is a *hot* primitive — essentially a coroutine-safe, suspending queue used to pass elements **between coroutines**. "Hot" means it is alive and accepts `send()` calls regardless of whether anyone is receiving. Elements may be buffered (depending on capacity) and sit there until received. ## Cold vs hot, precisely - **Cold (Flow):** producer is *lazy*, *per-collector*, and *replayable*. No collector ⇒ no work. Two collectors ⇒ two independent executions. - **Hot (Channel):** producer runs independently of consumers; state/elements live in the channel; values produced before a receiver attaches may be buffered or (if no room) suspend the sender. ## Single-consumption (the key Channel property) A plain `Channel` delivers **each element to exactly one receiver**. If two coroutines call `receive()`, the elements are **split** between them (fan-out), not duplicated. This is the opposite of `Flow`, where each collector sees the whole stream, and of `SharedFlow`, where each collector also sees a (shared) copy. ## Bridging a Channel into a Flow You often produce via a `Channel` (because production is push-based / inter-coroutine) but expose a `Flow` to callers: ```kotlin val channel = Channel<Int>() // receiveAsFlow: multiple collectors COMPETE for elements (single-consumption preserved) val flow1 = channel.receiveAsFlow() // consumeAsFlow: single collection only; cancels/closes the channel when collection ends val flow2 = channel.consumeAsFlow() ``` - `receiveAsFlow()` keeps single-consumption semantics: if collected by several collectors, they **compete** for elements; it does **not** close the channel. - `consumeAsFlow()` is **single-shot**: it cancels the channel after the collector finishes and throws if collected more than once. ## When to use which - Use a **Channel** for one-coroutine-to-another handoff, work queues, pipelines. - Use a **Flow** for declarative transformation chains, replayable cold sources, and APIs many collectors consume. - For hot, multi-collector broadcast state, prefer `StateFlow` / `SharedFlow` over a raw Channel.
- If two coroutines collect the same plain Channel via receiveAsFlow, do both see every element?No. They compete; each element goes to exactly one of them (fan-out). Only Flow/SharedFlow give every collector the full stream.
- Why does collecting a cold Flow twice run the producer twice?Because the Flow holds the producer lambda, not buffered results; collect simply invokes that lambda fresh each time.
Flow is a song sheet anyone can replay from the top; a Channel is a conveyor belt where each item is grabbed by one worker.
saying these in an interview costs you the question
- Says a plain Channel broadcasts each element to all receivers
- Claims a Flow buffers values and replays them by default
- Thinks Flow emits without a terminal operator
- Confuses Channel with SharedFlow/StateFlow
- Says Channel and Flow are interchangeable with no semantic difference