Explain reduce and fold as terminal operators on Flow. How do they differ, and what happens with an empty flow?
answer
- fold = you give the seed; reduce = first element is the seed
- reduce on empty -> NoSuchElementException
- fold on empty -> returns initial
- fold result type can differ; reduce stays same type
- Both lambdas are suspend
basics
~10 sBoth combine all emitted values into one result. fold starts from a value you provide; reduce starts from the first emitted value. reduce throws on an empty flow; fold returns the initial value.
solid answer
~40 sreduce and fold are suspending terminal operators that aggregate every element into a single result, like their collection counterparts. fold(initial) { acc, value -> ... } seeds the accumulator with initial, so the result type can differ from the element type and an empty flow simply returns initial. reduce { acc, value -> ... } uses the first emitted element as the seed and folds the rest in, so its result type matches the element type and an empty flow throws NoSuchElementException (there is no first value to start from). Both are inline suspend operators, so the lambda may itself call suspend functions. Choose fold when you have a neutral starting value or need a different accumulator type; choose reduce for non-empty same-type aggregation like summing or finding a max.
code
kotlin · 10 linessuspend fun aggregates() {
val nums = flowOf(2, 4, 6)
println(nums.reduce { acc, v -> acc + v }) // 12
println(nums.fold(StringBuilder()) { sb, v -> // "2,4,6,"
sb.append(v).append(','); sb
})
// empty handling
println(emptyFlow<Int>().fold(100) { a, v -> a + v }) // 100
// emptyFlow<Int>().reduce { a, v -> a + v } // would throw
}go deeper
Knows both combine elements into one value and that fold takes a starting value.
Correctly states the empty-flow behavior (reduce throws, fold returns initial) and the type-flexibility difference.
Notes the suspend lambdas, supertype signature of reduce, and picks the operator from emptiness/type requirements deliberately.
Considers correctness/observability of empty streams in pipelines and whether aggregation should even be a terminal vs a hot accumulator like StateFlow.
## Purpose Both are **suspend** terminal operators that collapse a `Flow<T>` into a single accumulated value by applying a binary operation across all elements — the streaming analogue of `Iterable.reduce`/`fold`. ## fold ```kotlin suspend fun <T, R> Flow<T>.fold( initial: R, operation: suspend (acc: R, value: T) -> R ): R ``` - Seeds the accumulator with **`initial`** (you supply it). - The accumulator type **`R` can differ** from the element type `T`. - On an **empty flow** it returns `initial` (no exception). ```kotlin val text: String = flowOf(1, 2, 3) .fold("start") { acc, v -> "$acc-$v" } // "start-1-2-3" val emptySum = emptyFlow<Int>().fold(0) { a, v -> a + v } // 0 ``` ## reduce ```kotlin suspend fun <S, T : S> Flow<T>.reduce( operation: suspend (acc: S, value: T) -> S ): S ``` - Uses the **first emitted element** as the initial accumulator, then folds the rest. - Result type is the **same as (a supertype of) the element type**. - On an **empty flow** it throws **`NoSuchElementException`** — there is no first element to seed with. ```kotlin val sum = flowOf(1, 2, 3, 4).reduce { acc, v -> acc + v } // 10 emptyFlow<Int>().reduce { a, v -> a + v } // throws NoSuchElementException ``` ## Side-by-side | | seed | result type | empty flow | |---|---|---|---| | `fold(initial)` | you provide `initial` | can differ (`R`) | returns `initial` | | `reduce` | first element | same as element | throws `NoSuchElementException` | ## Suspending lambdas Both `operation` lambdas are themselves `suspend`, so you may call other suspend functions inside the accumulation (e.g. await something per element) — though that serializes the work. ## When to pick which - **fold** — you have a natural neutral/identity value, or the result type differs from the elements (e.g. building a list/string/map), or the flow may legitimately be empty. - **reduce** — same-type aggregation where empties are unexpected (sum, max, concatenation of a known-non-empty stream).
- You need to sum a flow that might be empty. reduce or fold?fold(0) { acc, v -> acc + v } — it returns 0 for an empty flow, whereas reduce would throw NoSuchElementException.
- Can fold change the result type relative to the elements?Yes. fold's accumulator type R is independent of element type T, so you can fold Ints into a String, List, or any R.
fold brings its own empty bucket to start filling; reduce uses the first item it finds as the bucket — so with nothing to find, reduce has no bucket and fails.
saying these in an interview costs you the question
- Saying reduce returns a default on empty (it throws)
- Claiming fold cannot change the result type
- Thinking the accumulator lambdas cannot suspend
- Confusing which one needs an explicit initial value
- Believing fold throws on empty