You expose a producer's output as receiveAsFlow() and two coroutines collect it concurrently. Each collector expects to see every element. Why is this broken, and how do you fix it?
answer
- Channel fan-out, not broadcast
- Collectors compete -> split elements
- Broadcast = SharedFlow / shareIn
- Latest-value broadcast = StateFlow
- shareIn(scope, SharingStarted, replay)
basics
~20 sA Channel gives each item to only one receiver, so the two collectors split the elements instead of both seeing them all. To give every collector the full stream, use a SharedFlow (or shareIn) instead of a raw channel.
solid answer
~40 s`receiveAsFlow()` preserves the Channel's single-consumption semantics: each emitted element is received by exactly one collector, so two concurrent collectors **compete** and split the stream (fan-out) rather than each getting a full copy. That's correct behavior for a channel but wrong for the 'both see everything' expectation. The fix is a **hot broadcast** abstraction: convert to a `SharedFlow` via `shareIn(scope, started, replay)`, or model the source as a `MutableSharedFlow` and `emit` to it. Each `SharedFlow` collector gets its own copy of every element. If you need the latest value plus updates, use `StateFlow`. Do not try to fix it by adding more channels manually unless you intentionally want fan-out.
code
kotlin · 10 lines// BROKEN expectation: both collectors want all values, but they compete
val flow = channel.receiveAsFlow()
launch { flow.collect { println("A:$it") } }
launch { flow.collect { println("B:$it") } } // A and B SPLIT elements
// FIX: multicast with shareIn
val shared = channel.receiveAsFlow()
.shareIn(scope, SharingStarted.WhileSubscribed(), replay = 0)
launch { shared.collect { println("A:$it") } }
launch { shared.collect { println("B:$it") } } // both see every elementgo deeper
Recognizes the two collectors don't both get everything and that some broadcast mechanism is needed.
Names SharedFlow/shareIn as the fix and explains fan-out vs broadcast clearly.
Discusses SharingStarted policies, replay/buffer sizing, and when fan-out is the intended design instead of a bug.
Weighs StateFlow vs SharedFlow vs channel for the actual product requirement, backpressure and lifecycle (scope) implications of shareIn.
## Why it's broken A plain `Channel<T>` is **single-consumption**: an element sent with `send()` is delivered to **exactly one** `receive()`. `receiveAsFlow()` faithfully exposes that. So when two coroutines collect the same `receiveAsFlow()` Flow at once, they **compete** for elements — element 1 might go to collector A, element 2 to collector B, etc. This is **fan-out**, useful for distributing work, but it is **not** what 'every collector sees every element' requires. ## What you actually want 'Every collector sees every element' is **broadcast / multicast** semantics. Kotlin's hot multicast primitive is `SharedFlow` (and its stateful variant `StateFlow`). Each collector of a `SharedFlow` receives its own copy of every emission. ## Fix 1 — shareIn ```kotlin val upstream: Flow<Int> = channel.receiveAsFlow() val shared: SharedFlow<Int> = upstream.shareIn( scope = scope, started = SharingStarted.WhileSubscribed(), replay = 0, ) // every collector of `shared` now gets every element ``` `shareIn` launches one collection of the upstream in `scope` and multicasts it. `SharingStarted` controls when the upstream runs (`Eagerly`, `Lazily`, `WhileSubscribed`). `replay` controls how many recent values late subscribers get. ## Fix 2 — MutableSharedFlow directly ```kotlin val events = MutableSharedFlow<Int>(replay = 0, extraBufferCapacity = 64) // producer: events.emit(value) // every collector of events.asSharedFlow() sees every value ``` ## Fix 3 — StateFlow for current-value semantics If collectors need the *latest* value immediately plus subsequent updates, use `MutableStateFlow`/`StateFlow`; it conflates and always has a current value. ## Summary - Channel / `receiveAsFlow()` -> **fan-out** (one element, one collector). - `SharedFlow` / `shareIn` -> **broadcast** (one element, all collectors). - `StateFlow` -> broadcast of the latest value with conflation. Pick by whether you want to **split** work or **duplicate** the stream.
- When is the competing fan-out behavior actually the right design?When distributing work across multiple consumers — e.g., a job queue where each task should be handled by exactly one worker.
- What does SharingStarted.WhileSubscribed() change versus Eagerly?WhileSubscribed only runs the upstream while there is at least one collector and can stop it when none remain; Eagerly starts immediately and keeps it running.
A channel is a single ticket queue: each customer is served once. SharedFlow is a radio broadcast: every listener hears the same thing.
saying these in an interview costs you the question
- Claims receiveAsFlow already broadcasts and the code is fine
- Tries to 'fix' it by collecting twice expecting duplication
- Confuses fan-out with broadcast
- Suggests StateFlow when full element history per collector is needed but never mentions replay/conflation
- Doesn't know SharedFlow/shareIn exist