Member-only story
Tricky Android Interview Questions: Flow & StateFlow Edition
Other articles in this series
Tricky Android Interview Questions:
• Kotlin Coroutines Edition
• Flow & StateFlow Edition ← you’re here
• Jetpack Compose Effects Edition
• ViewModel & State Handling Edition
• Kotlin Object & Data Class Edition
This isn’t another “how to use Flow” article.
This is for those interview moments when the question seems simple — but turns out not to be.
Like when someone asks what happens if you call delay() inside collectLatest.
The code compiles. It even runs. But the behavior? It’s not what most developers expect — unless they’ve already run into it.
This article is a collection of tricky Flow and StateFlow questions — not trivia, not gotchas, but real examples that test how well you understand what’s happening under the surface.
If you’re preparing for interviews, or just want to check whether your mental model of Flow matches reality, you’ll find something useful here.
Question 1: What happens if you call delay() inside collectLatest?
Most developers understand that collectLatest cancels the previous block when a new value is emitted. But what actually happens if that block contains a suspending function like delay()?
Here’s an example that illustrates this clearly:
fun main() = runBlocking {
val flow = flowOf(1, 2, 3)
flow
.onEach { delay(90) } // simulating heavy upstream work (e.g. API, DB)
.collectLatest { value ->
println("Start $value")
delay(100) // simulating processing of each item
println("End $value")
}
}In this setup:
delay(90)simulates upstream latency — for example, a delay between incoming events, API responses, or database reads.delay(100)insidecollectLatestrepresents the time it takes to process each item.
Because each new item arrives slightly before the previous one has finished processing, collectLatest cancels the current block before it completes.
Why this is a trick question
It’s easy to assume that collectLatest just skips items that arrive too fast. But it does more than that — it actively cancels the previous block of code, including any suspend calls like delay().
If you’re not aware of this behavior, it’s natural to assume the collector runs sequentially and completes each block.
What actually happens
Here’s the output:
Start 1
Start 2
Start 3
End 3Only the last value is fully processed. All previous values are interrupted during the delay and never reach the End log line.
What to remember
collectLatestcancels the entire lambda when a new value is emitted.- Any suspending operation inside — including
delay(),withContext, or long-running work like network or database access — will be cancelled. - Use
collectLatestonly when intermediate results can be safely discarded.
Bonus insight
It’s not just delay() that gets cancelled. If you perform network or database operations inside collectLatest, those too will be interrupted if a new value arrives.
That means:
- Requests may be aborted before completion
- Database writes might never finish
- Side effects can silently disappear
If the result of an operation matters and must be completed, collectLatest is the wrong choice.
In those cases, use collect {} and manage cancellation manually if needed.
Question 2: What happens if you call collect() twice on the same Flow?
Most developers know that Flow is cold by default. But not everyone fully realizes what that means in practice — especially when the same flow is collected more than once.
Consider this example:
val flow = flow {
println("Flow started")
emit(1)
}
flow.collect { println("First: $it") }
flow.collect { println("Second: $it") }The code is valid. It compiles. It runs without errors.
But the behavior? Not what you might expect — unless you understand how cold flows work.
Why this is a trick question
It’s easy to assume that calling collect() twice on the same flow simply reuses the data. But that's not how Flow behaves.
Each call to collect() starts the flow from scratch.
That means the block inside flow {} is executed again — including any side effects or long-running operations.
In this example, println("Flow started") runs twice, even though we only defined the flow once.
What actually happens
Flow started
First: 1
Flow started
Second: 1Each call to collect() triggers a new execution of the flow builder block.
If the flow includes something expensive — like a network request or database read — that operation will be repeated as well.
What to remember
Flowis cold — each time you callcollect(), it restarts from the beginning.- Side effects inside
flow {}are not shared between collectors. - To share a single execution across collectors, use
shareIn()orstateIn().
Question 3: What happens if a Flow throws an exception?
Many developers assume that collect {} simply processes values — and that even if something goes wrong, the flow will continue with the next value.
But what actually happens when a flow throws an exception during emission?
Let’s look at a minimal example:
val flow = flow {
emit(1)
throw RuntimeException("Something went wrong")
}
flow.collect { value ->
println("Received: $value")
}The code looks simple: emit one value, then throw an exception.
But the way Flow handles errors might not match your expectations.
Why this is a trick question
It’s easy to assume that collect {} will handle values as they come, and that errors would somehow be ignored unless explicitly caught.
In reality, if a Flow throws an uncaught exception, the collection is immediately cancelled.
No further emissions are processed, and no further code inside collect will execute.
Unless you handle the exception manually, the entire flow will crash.
What actually happens
Here’s the output:
Received: 1
Exception in thread "main" java.lang.RuntimeException: Something went wrongThe first value is collected and printed.
But when the exception is thrown, the flow terminates immediately.
If there were more emissions after the exception, they would never be processed.
What to remember
- Unhandled exceptions inside a flow cancel the collection immediately.
- No further emissions are processed after an exception.
- To handle errors gracefully, use the
catch {}operator beforecollect().
Example:
flow
.catch { e -> println("Caught error: ${e.message}") }
.collect { value -> println("Received: $value") }You might also like:
Enjoyed this article?
If it helped you, or made you rethink how Flow and StateFlow work — consider leaving a clap. It helps others discover the article.
If you’re into Android interviews, Kotlin internals, or enjoy exploring subtle edge cases — feel free to follow.
More tricky questions (and answers) are coming soon.
Anatolii Frolov
Senior Android Developer
Writing honest, real-world Kotlin & Jetpack Compose insights.
📬 Follow me on Medium









