Flow & Operators Quiz
KOTLIN › Concurrency
Two coroutines each call collect on the same cold Flow built with flow { ... }. What happens?
- The producer block runs once and both collectors share the emissions
- The producer block runs independently for each collector
- The second collect throws because the flow is already active
- Only the first collector receives values; the second gets nothing
Answer: The producer block runs independently for each collector
Cold flows are lazy: each terminal operator triggers a fresh, independent execution of the producer. Sharing requires shareIn/stateIn (hot flows).
Given upstream.map { }.flowOn(Dispatchers.IO).collect { }, where does the collect lambda run?
- On Dispatchers.IO, because flowOn applies to the whole chain
- On Dispatchers.Default by default
- In the context of the coroutine that called collect
- On Dispatchers.Main always
Answer: In the context of the coroutine that called collect
flowOn is context-preserving and only affects the upstream (the map and producer). Downstream operators and the collector run in the collector's own context.
Which statement about the catch operator is correct?
- It catches exceptions thrown inside the collect { } lambda
- It catches exceptions from operators placed below it in the chain
- It only catches exceptions emitted by the upstream flow above it
- It silently swallows all exceptions and cannot re-emit values
Answer: It only catches exceptions emitted by the upstream flow above it
catch is restricted to upstream exceptions. It cannot see the collect lambda or downstream operators, and inside it you may rethrow or emit a fallback.
You want a search field to cancel the previous network request whenever a new query arrives. Which operator fits best?
- flatMapLatest, which cancels the previous request on each new query
- flatMapConcat, which queues each query and runs them in strict arrival order
- zip, which pairs each new query with its matching search result item
- buffer, which stores pending queries so that none are ever dropped
Answer: flatMapLatest, which cancels the previous request on each new query
flatMapLatest cancels the previous inner flow when a new upstream value is emitted, which is exactly the search-as-you-type pattern. flatMapConcat would queue them instead.
What is the key behavioral difference between zip and combine?
- zip emits when either flow changes; combine matches items one by one
- combine emits latest values on any update; zip matches items one by one
- They are just aliases for the same operator and behave exactly alike
- zip runs flows one after the other; combine puts them on different threads
Answer: combine emits latest values on any update; zip matches items one by one
combine fires whenever either source emits (after both have produced at least one value), using the latest of each. zip strictly pairs values and waits for both.
Inside a flow { } builder, why does wrapping emit() in withContext(Dispatchers.IO) fail?
- withContext is suspend, but it still can't switch dispatchers inside flow
- Dispatchers.IO can't be used for emissions because Flow blocks background work
- buffer fixes context changes by moving emit onto the collector thread
- It breaks Flow context preservation; emit must stay in the builder context
Answer: It breaks Flow context preservation; emit must stay in the builder context
Flow enforces context preservation and throws if emit happens in a different context. Use flowOn to change the producer's dispatcher, or channelFlow/callbackFlow to emit from elsewhere.
A slow collector processes a fast producer, but you only ever need the most recent value and want to drop the ones in between. Which operator do you use?
- buffer
- conflate
- retry
- distinctUntilChanged
Answer: conflate
conflate keeps only the latest value, skipping intermediates the collector couldn't keep up with. buffer would queue all of them; distinctUntilChanged only removes consecutive duplicates.
Which of the following is a terminal operator that actually triggers collection of the upstream flow?
- toList, a terminal operator that collects the flow and starts it
- map, an intermediate operator that lazily transforms each emitted value
- filter, an intermediate operator that lazily drops non-matching values
- onEach, an intermediate operator that peeks at each value lazily
Answer: toList, a terminal operator that collects the flow and starts it
toList is a terminal suspend operator that collects the flow into a list, starting execution. map, filter, and onEach are intermediate operators that return a new Flow lazily without running anything.
You wrap a callback-based location API as a Flow using callbackFlow. What must the builder block do at the end to behave correctly?
- Return the last emitted value as the block's result and finish
- Call awaitClose { } to wait for cancel and remove the callback
- Call flowOn(Dispatchers.IO) as the final statement in the block
- Throw CancellationException manually to stop the stream early
Answer: Call awaitClose { } to wait for cancel and remove the callback
callbackFlow requires awaitClose at the end; it keeps the producer alive until the collector cancels and provides a place to remove the listener. Omitting it throws IllegalStateException at runtime.
Using collectLatest, what happens when a new value arrives before the previous collector block has finished processing?
- The new value is queued and each block runs strictly in order
- The new value is dropped and the running block keeps going
- The running block is cancelled and restarted with the new value
- Both blocks keep running at the same time on separate dispatchers
Answer: The running block is cancelled and restarted with the new value
collectLatest cancels the in-progress collector block whenever a newer value is emitted and relaunches it with that value, so only the processing of the latest value runs to completion.
In a search box you want to wait until the user pauses typing for 300ms before firing a query, discarding intermediate keystrokes. Which operator fits best?
- conflate
- sample
- distinctUntilChanged
- debounce
Answer: debounce
debounce(300) only emits a value once that quiet window elapses with no new emissions. sample emits the latest value on a fixed interval, and distinctUntilChanged only filters consecutive duplicates.
You want to convert a cold Flow into a hot stream that always holds a current value and replays it to every new collector while sharing a single upstream collection. Which operator do you use?
- shareIn
- buffer
- stateIn
- produceIn
Answer: stateIn
stateIn turns a cold flow into a StateFlow that always has a current value and emits it to new subscribers. shareIn produces a SharedFlow without an always-present value, and buffer does not make the flow hot at all.
flatMapConcat, flatMapMerge, and flatMapLatest all flatten a flow of flows. Which one collects the inner flows one at a time, preserving upstream order and waiting for each to complete?
- flatMapMerge, which collects all the inner flows concurrently without order
- flatMapConcat, collecting inner flows one at a time in upstream order
- flatMapLatest, which cancels the previous inner flow on each new value
- zip, which pairs values from two separate flows position by position
Answer: flatMapConcat, collecting inner flows one at a time in upstream order
flatMapConcat processes inner flows sequentially in upstream order, fully collecting one before starting the next. flatMapMerge runs them concurrently without ordering, and flatMapLatest cancels the previous inner flow.
An exception is thrown inside your collect { } lambda. Why won't a catch operator placed above collect handle it, and what should you do?
- catch handles collector errors on its own, so no extra code is needed
- Add another catch below collect to intercept the lambda exception
- Wrap collect in try/catch; catch only sees upstream failures
- collectLatest swallows exceptions from the collector, so switch to it
Answer: Wrap collect in try/catch; catch only sees upstream failures
catch enforces exception transparency and only intercepts failures from upstream operators and the producer, never the collect lambda. To handle errors in the collector itself, wrap the collect call in a regular try/catch.