In Kotlin Flow, what does 'context preservation' mean, and in which coroutine context does emit run by default?
answer
- emit runs in the collector's context
- Producer can't switch threads on its own
- No withContext around emit
- Use flowOn to change upstream
- Violation throws IllegalStateException
basics
~10 sIt means the code that produces values runs in the same place that collects them. By default, emit runs in the collector's coroutine, so the flow uses whatever context the collector started in.
solid answer
~30 sContext preservation is the Flow guarantee that a flow's emissions happen in the same CoroutineContext as the collector. When you call collect from a coroutine, the body of flow { ... } and every emit run inside that collector's coroutine and its context (its CoroutineDispatcher, Job, etc.). The producer doesn't get to silently switch threads. This makes flows predictable: side effects in the builder run where you collect. To run upstream work on a different dispatcher you must use the dedicated flowOn operator rather than wrapping emit in withContext, which would break preservation and throw an IllegalStateException at runtime.
code
kotlin · 13 linesimport kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val f = flow {
println("emit on ${Thread.currentThread().name}")
emit(1)
}
withContext(Dispatchers.Default) {
f.collect { println("collect on ${Thread.currentThread().name}") }
}
// emit and collect both report a Default-dispatcher thread
}go deeper
Knows emit runs in the collector's context and that flows don't switch threads by themselves.
Explains the invariant, knows withContext around emit is illegal, and names flowOn as the fix.
Connects context preservation to structured concurrency, cancellation, and predictable side effects.
Frames preservation as a design contract enabling local reasoning and safe composition across teams/modules.
## What 'context preservation' means A **CoroutineContext** is the bundle of data a coroutine runs with — most importantly its **CoroutineDispatcher** (which thread/threadpool runs the code) and its **Job**. *Context preservation* is a core Flow rule: the values a flow emits are produced in **the same context in which the flow is collected**. Concretely, when you write: ```kotlin val f = flow { emit(1) // runs in the COLLECTOR's context emit(2) } withContext(Dispatchers.Default) { f.collect { println(it) } // collected on Default... } // ...so emit also runs on Default ``` the `flow { ... }` block and each `emit` execute inside the collector's coroutine. The producer is **not** allowed to change the context on its own. ## Why this matters - **Predictability:** any side effect or `emit` happens where you collect — no hidden thread hops. - **Structured concurrency:** the flow shares the collector's `Job`, so cancellation propagates correctly. - **Safety:** UI frameworks can rely on a flow collected on the main dispatcher staying on the main thread. ## The wrong way to switch threads Do NOT wrap `emit` in `withContext`: ```kotlin flow { withContext(Dispatchers.IO) { emit(load()) // ILLEGAL: emit from a different context } } ``` This violates preservation. Flow detects it and throws **`IllegalStateException`** with a message like "Flow invariant is violated: emission from another coroutine context". ## The right way: flowOn Use the **`flowOn(dispatcher)`** operator to change the **upstream** context without breaking the invariant (covered in depth in other questions). `flowOn` changes where producer code runs while still delivering emissions correctly to the collector. ## Key APIs/keywords - `flow { }` builder and `emit` (a `suspend` function) - `collect` (terminal, `suspend`) - `CoroutineContext`, `CoroutineDispatcher`, `Dispatchers.IO/Default/Main` - `flowOn` for legal upstream context change - `withContext` — legal in general, but NOT around `emit`
- Is emit a suspend function?Yes. emit is a suspend function; it can only be called from the flow builder's suspending block and cooperates with backpressure and cancellation.
- If you collect on Dispatchers.Main, where does the flow {} body run?On Dispatchers.Main — the collector's context. The producer code shares that dispatcher unless you add flowOn.
It's like a kitchen rule: cooks must plate the dish at the same counter where the waiter picks it up — they can't quietly move to another counter mid-service.
saying these in an interview costs you the question
- Claiming the flow builder always runs on a background thread by default
- Saying you should use withContext(IO) around emit to do IO
- Thinking emit can be called from any coroutine you like
- Confusing collect's context with the flow's declaration site