What do the Flow operators onStart and onCompletion do, and when does each block run?
answer
- onStart = before first emit
- onCompletion = after end, always
- cause: null / cancel / failure
- Both can emit() values
- onCompletion observes, doesn't catch
basics
~10 sonStart runs once right before a flow starts producing values. onCompletion runs once after the flow finishes, whether it ended normally, was cancelled, or threw an error.
solid answer
~40 sBoth are intermediate operators on Flow. onStart { } executes its block in the collector's context just before collection begins and can even emit() extra values first. onCompletion { cause -> } executes after the upstream finishes for any reason; its nullable Throwable parameter is null on success, the CancellationException on cancellation, or the failure exception otherwise. onCompletion can also emit() a final value. Neither catches exceptions: onCompletion only observes the cause and rethrows it, so you still need catch or a try/finally around collect for handling. They are commonly used to toggle loading spinners: onStart shows the spinner, onCompletion hides it regardless of outcome.
code
kotlin · 7 linesflow {
emit(1)
emit(2)
}
.onStart { emit(0) } // prepends 0
.onCompletion { cause -> println("end: $cause") } // cause == null here
.collect { println(it) } // prints 0,1,2 then "end: null"go deeper
Knows onStart runs before, onCompletion runs after for any outcome, and uses them for spinners.
Adds that both run in the collector context, can emit(), and that onCompletion's cause distinguishes success/cancel/failure.
Stresses onCompletion observes-not-catches semantics and pairs it correctly with catch ordering.
Frames them as declarative lifecycle hooks vs try/finally and reasons about context preservation and exception transparency guarantees.
## What is a Flow A `Flow<T>` is Kotlin's cold asynchronous stream: nothing runs until a terminal operator such as `collect` is called. `onStart` and `onCompletion` are *intermediate* operators (they return a new `Flow` and don't trigger execution themselves) used to hook into the start and end of that collection. ## onStart ```kotlin flowOf(1, 2, 3) .onStart { println("about to start") } .collect { println(it) } ``` - The block runs **once**, **before** the upstream flow emits its first value, in the **collector's coroutine context**. - The lambda has an `FlowCollector<T>` receiver, so you may call `emit()` inside it to prepend values: `.onStart { emit(0) }` emits `0` before the upstream values. - Typical use: show a loading indicator, log, or seed an initial value. ## onCompletion ```kotlin flowOf(1, 2, 3) .onCompletion { cause -> println("done, cause=$cause") } .collect { println(it) } ``` - The block runs **once**, **after** the flow stops producing values, no matter how it stopped. - The single parameter `cause: Throwable?` tells you *why* it ended: - `null` — the flow completed **normally**. - a `CancellationException` — the collecting coroutine was **cancelled**. - any other `Throwable` — the flow (or downstream) **failed** with that exception. - Like `onStart`, the lambda is a `FlowCollector<T>`, so you can `emit()` a final value (only safe if the cause is null/non-fatal). ## Key rule: onCompletion does NOT swallow exceptions `onCompletion` only **observes** the terminating cause and then **rethrows** it. It is like a `finally` block, not a `catch`. To actually handle the exception you still need the `catch` operator upstream of completion, or a `try/finally` around `collect`. ```kotlin flow { emit(1); throw RuntimeException("boom") } .onCompletion { cause -> println("cleanup, cause=$cause") } // prints boom, then rethrows .catch { e -> println("handled $e") } // place catch AFTER onCompletion to handle it .collect() ``` ## Why prefer these over try/finally - They keep lifecycle logic in the operator chain, attach to the **specific flow**, and run in the collector context, making spinner-toggle code symmetric and declarative.
- Can onStart emit values, and where do they appear in the stream?Yes. Its lambda is a FlowCollector, so emit() inside onStart prepends those values before any upstream emissions reach the collector.
- If a flow throws, does the onCompletion block stop the exception from propagating?No. onCompletion sees the cause but rethrows it; you still need catch or try/finally to handle the error.
onStart/onCompletion are the 'lights on' and 'lights off' switches that flank a stage performance, no matter how the show ends.
saying these in an interview costs you the question
- Saying onCompletion catches/handles the exception
- Thinking onStart runs after the first emission
- Claiming onCompletion only runs on success
- Confusing onCompletion's cause with the emitted value
- Believing these operators trigger collection on their own