What is the difference between fold and reduce in Kotlin, and when must you choose fold?
answer
- reduce seeds with element[0] → throws on empty
- fold takes explicit seed → empty-safe, type can change
- reduceOrNull avoids the exception
- fold lets accumulator type differ from element type
- foldRight/reduceRight go right-to-left
basics
~20 sBoth combine elements into one result. fold takes a starting value, so it works on empty collections and can return a different type. reduce uses the first element as the start, so it throws if the collection is empty.
solid answer
~40 sreduce(operation) seeds the accumulator with the first element, then folds the rest; on an empty collection it throws UnsupportedOperationException, and the accumulator type must match the element type. fold(initial, operation) takes an explicit initial accumulator, so it is safe on empty input (returns the initial) and lets the result type differ from the element type — e.g. folding a List<Int> into a StringBuilder or a Map. The lambda signature differs: reduce gives (acc, element) where acc starts as element[0]; fold gives (acc, element) where acc starts as initial. Use reduceOrNull() to get null instead of an exception on empty input. Prefer fold when you need a typed seed, a type change, or empty-safety; reduce is a concise shorthand only when the collection is guaranteed non-empty and the result type equals the element type.
code
kotlin · 10 lines// reduce: same type, throws on empty
val product = listOf(1, 2, 3, 4).reduce { acc, x -> acc * x } // 24
// fold: change type, empty-safe
val csv = listOf("a", "b", "c")
.fold(StringBuilder()) { sb, s -> sb.append(s).append(',') }
.toString() // "a,b,c,"
val safe = emptyList<Int>().fold(0) { a, b -> a + b } // 0
val n = emptyList<Int>().reduceOrNull { a, b -> a + b } // nullgo deeper
Knows both combine elements; may not recall the empty-collection behavior.
Clearly states reduce throws on empty and fold needs a seed and can change type.
Chooses correctly under constraints (type change, empty-safety) and mentions reduceOrNull and indexed/right variants.
Reasons about determinism/left-associativity and when an explicit fold beats a domain-specific helper like sumOf for clarity.
## The shared idea Both `fold` and `reduce` are **left-to-right accumulations**: they walk the collection carrying an accumulator and combine it with each element to produce a single final value. ## reduce ```kotlin listOf(1, 2, 3, 4).reduce { acc, x -> acc + x } // 10 ``` - The **first element becomes the initial accumulator**; the lambda is first called with `(element[0], element[1])`. - Therefore the accumulator type **must equal** the element type. - On an **empty** collection it throws `UnsupportedOperationException` ("Empty collection can't be reduced"). - `reduceOrNull()` returns `null` instead of throwing. - `reduceIndexed { index, acc, x -> ... }` exposes the index. ## fold ```kotlin listOf(1, 2, 3).fold("=") { acc, x -> acc + x } // "=123" ``` - You pass an **explicit initial value** (the seed) as the first argument. - The accumulator type is the **type of the seed**, which can differ from the element type — this is how you turn a `List<Int>` into a `String`, `Map`, `StringBuilder`, etc. - Safe on empty input: it simply returns the seed. - `foldIndexed`, and right-to-left variants `foldRight` / `reduceRight` exist. ## Decision rule | Need | Use | |------|-----| | Empty-safe | `fold` (or `reduceOrNull`) | | Result type ≠ element type | `fold` | | Custom seed (e.g. start at 100) | `fold` | | Non-empty + same type, concise | `reduce` | ## Common pitfall Developers reach for `reduce` to sum and crash on empty input in production. Either guard emptiness, use `fold(0) { a, b -> a + b }`, or simply use `sum()`. ## Associativity note Kotlin's `fold`/`reduce` are strictly **sequential and left-associative** (not parallel), so order-dependent operations (string concatenation, subtraction) are deterministic.
- Why can fold return a Map but reduce cannot?fold's accumulator type is the seed type, so seeding with emptyMap() lets you build a Map. reduce forces the accumulator to be the element type, so it can't change types.
- How do you make reduce safe on a possibly-empty list without fold?Use reduceOrNull(), which returns null instead of throwing UnsupportedOperationException.
fold is starting a snowball from a stone you bring; reduce starts the snowball from the first lump of snow you find — and panics if there's no snow.
saying these in an interview costs you the question
- Saying reduce returns null on empty (it throws unless you use reduceOrNull)
- Claiming reduce can change the result type
- Not knowing fold takes an initial seed argument
- Believing fold/reduce run in parallel or in unspecified order