Question

fold() vs reduce() in Kotlin: Difference, Examples, and When to Use Each

kotlinreducefold

Question

I am confused about the difference between fold() and reduce() in Kotlin. Can someone provide a clear example that shows how they differ and when each one should be used?

Short Answer

By the end of this page, you will understand what fold() and reduce() do in Kotlin, how they differ, and when to choose one over the other. You will also see practical examples, common mistakes, and a small mini-project to help you apply the idea.

Concept

fold() and reduce() are Kotlin collection functions used to combine many values into one final result.

For example, you might want to:

  • add all numbers in a list
  • build a sentence from words
  • count values
  • transform a list into a map or string

Both functions process items one by one and keep an accumulator value as they go.

The key difference

reduce()

reduce() uses the first element of the collection as the starting accumulator.

That means:

  • it only works safely when the collection is not empty
  • the accumulator type is usually the same as the element type

fold()

fold() lets you provide your own initial value.

That means:

  • it works on empty collections too
  • the accumulator can be a different type from the elements

Why this matters

In real programs, collections are often dynamic. Sometimes they are empty. Sometimes the result type is not the same as the item type.

Examples:

  • summing prices into a Double
  • building a formatted String
  • collecting values into a Map
  • counting items into an Int

In these cases, fold() is often more flexible.

Mental rule

  • Use reduce() when the first item can logically act as the starting result.
  • Use fold() when you need a custom starting value or need to handle empty collections safely.

Mental Model

Think of both functions like filling out a running total on paper.

  • With reduce(), you start by writing down the first number in the list.
  • With fold(), you start by writing down whatever initial value you choose.

Imagine a list:

listOf(2, 3, 4)

Using reduce() is like saying:

  • start with 2
  • combine with 3
  • combine with 4

Using fold(10) is like saying:

  • start with 10
  • combine with 2
  • combine with 3
  • combine with 4

So reduce() starts from the collection, while fold() starts from you.

Take Quiz

Syntax and Examples

Basic syntax

reduce()

val result = list.reduce { acc, item ->
    acc + item
}
  • acc is the current accumulated value
  • item is the current element from the list
  • the first element becomes the initial acc

fold()

val result = list.fold(initialValue) { acc, item ->
    acc + item
}
  • initialValue is provided by you
  • acc starts as that initial value

Example 1: Sum numbers

val numbers = listOf(1, 2, 3, 4)

val sumWithReduce = numbers.reduce { acc, n -> acc + n }
val sumWithFold = numbers.fold(0) { acc, n -> acc + n }

println(sumWithReduce) // 10
println(sumWithFold)   // 10

Both produce the same result here.

Example 2: Start from a custom value

val numbers = listOf(1, 2, 3, 4)
val result = numbers.fold(100) { acc, n -> acc + n }

println(result) // 110

This is something reduce() cannot do directly, because reduce() always starts with the first element.

Example 3: Different result type

val numbers = listOf(1, 2, 3)

val text = numbers.fold("Numbers:") { acc, n ->
    "$acc $n"
}

println(text) // Numbers: 1 2 3

This is a strong reason to use fold(): the list contains Int, but the result is a String.

Example 4: Empty list behavior

val empty = emptyList<Int>()

val safeResult = empty.fold(0) { acc, n -> acc + n }
println(safeResult) // 0
val empty = emptyList<Int>()
val result = empty.reduce { acc, n -> acc + n } // throws exception

reduce() on an empty collection throws an exception because there is no first element to use as the initial accumulator.

Step by Step Execution

Consider this code:

val numbers = listOf(5, 10, 15)
val result = numbers.fold(0) { acc, n -> acc + n }
println(result)

Step by step

Step 1: Create the list

val numbers = listOf(5, 10, 15)

The list contains three integers.

Step 2: Start fold() with initial value 0

numbers.fold(0)

So before processing any elements:

  • acc = 0

Step 3: Process first element 5

acc + n = 0 + 5 = 5

Now:

  • acc = 5

Step 4: Process second element 10

acc + n = 5 + 10 = 15

Now:

  • acc = 15

Step 5: Process third element 15

acc + n = 15 + 15 = 30

Now:

  • acc = 30

Step 6: Return final result

println(result) // 30

Compare with reduce()

val numbers = listOf(5, 10, 15)
val result = numbers.reduce { acc, n -> acc + n }
println(result)

Execution looks like this:

  • start with first element: acc = 5
  • combine with 10 -> 15
  • combine with 15 -> 30

The final result is still 30, but the starting point is different.

Real World Use Cases

Where reduce() is useful

reduce() is useful when:

  • the collection is known to be non-empty
  • the result type should be the same as the element type
  • the first element is a natural starting point

Examples:

  • finding the total sum of a non-empty list of numbers
  • finding the maximum value in a non-empty list
  • combining objects of the same type
val numbers = listOf(3, 7, 2, 9)
val max = numbers.reduce { acc, n -> if (acc > n) acc else n }
println(max) // 9

Where fold() is useful

fold() is useful when:

  • the collection might be empty
  • you need a custom initial value
  • the result type differs from the element type

Examples:

  • summing cart prices starting from 0.0
  • building a CSV string
  • counting matching records
  • grouping data into a map
val words = listOf("Kotlin", "is", "fun")
val sentence = words.fold("") { acc, word ->
    if (acc.isEmpty()) word else "$acc $word"
}
println(sentence) // Kotlin is fun
Take Quiz

Real Codebase Usage

In real Kotlin projects, developers often choose fold() because it is safer and more flexible.

Common patterns

Guarding against empty collections

If a list can be empty, fold() avoids runtime errors.

val total = prices.fold(0.0) { acc, price -> acc + price }

Transforming while combining

You may want to turn a list into another structure.

val wordLengths = listOf("cat", "house", "sun")
    .fold(mutableMapOf<String, Int>()) { acc, word ->
        acc[word] = word.length
        acc
    }

Building configuration or summary objects

data class Stats(val count: Int, val total: Int)

val stats = listOf(2, 4, 6).fold(Stats(0, 0)) { acc, n ->
    Stats(acc.count + 1, acc.total + n)
}

Using reduce() when non-empty is guaranteed

If business logic guarantees at least one item, reduce() can be expressive.

val bestScore = scores.reduce { acc, score -> maxOf(acc, score) }

Practical advice

In many codebases:

  • choose fold() for safety and flexibility
  • choose reduce() when non-empty input is guaranteed and the first element is a natural starting point

Common Mistakes

1. Using reduce() on an empty list

This is the most common problem.

val numbers = emptyList<Int>()
val total = numbers.reduce { acc, n -> acc + n } // error at runtime

How to avoid it

Use fold() if the list may be empty.

val total = numbers.fold(0) { acc, n -> acc + n }

2. Expecting reduce() to accept a custom start value

Broken idea:

// reduce() does not take an initial value like this
val total = listOf(1, 2, 3).reduce(10) { acc, n -> acc + n }

Fix

Use fold().

val total = listOf(1, 2, 3).fold(10) { acc, n -> acc + n }

3. Using reduce() when the result type should be different

Broken approach:

val numbers = listOf(1, 2, 3)
// reduce is not the right choice for building a String result here

Fix

Use fold() with a String initial value.

val result = numbers.fold("Start") { acc, n -> "$acc $n" }

4. Forgetting what acc represents

Some beginners think acc is always the previous element. It is not.

It is the running result so far.

val result = listOf(1, 2, 3).fold(0) { acc, n -> acc + n }

Here:

  • acc becomes 0, then 1, then 3, then 6
  • it is not just the previous list item

5. Mutating and returning the wrong value in fold()

When using mutable accumulators, you must return the accumulator.

Broken code:

val result = listOf("a", "b").fold(mutableListOf<String>()) { acc, item ->
    acc.add(item)
    mutableListOf()
}

Fix

val result = listOf("a", "b").fold(mutableListOf<String>()) { acc, item ->
    acc.add(item)
    acc
}

Comparisons

Featurereduce()fold()
Initial valueUses first collection elementProvided by you
Works on empty collectionsNoYes
Result type can differ from item typeUsually noYes
Best forNon-empty collections with same-type resultFlexible accumulation and safe defaults
RiskThrows on empty listSafer for unknown input

Quick comparison example

val numbers = listOf(1, 2, 3)

val a = numbers.reduce { acc, n -> acc + n }    // 6
val b = numbers.fold(0) { acc, n -> acc + n }   // 6
val c = numbers.fold(10) { acc, n -> acc + n }  // 16

Related alternatives

FunctionUse when
sum()You just want the sum of numeric values
map()You want to transform each item into another item
filter()You want to keep only matching items
reduce()You want to combine a non-empty list into one value
fold()You want to combine values with a custom initial accumulator
Take Quiz

Cheat Sheet

Quick rules

  • reduce() starts with the first element
  • fold() starts with your initial value
  • reduce() fails on an empty collection
  • fold() works on an empty collection
  • use fold() when result type differs from element type

Syntax

list.reduce { acc, item ->
    // return new accumulator
}
list.fold(initialValue) { acc, item ->
    // return new accumulator
}

Examples

val sum1 = listOf(1, 2, 3).reduce { acc, n -> acc + n }
val sum2 = listOf(1, 2, 3).fold(0) { acc, n -> acc + n }
val text = listOf(1, 2, 3).fold("Numbers:") { acc, n -> "$acc $n" }

Empty collection

emptyList<Int>().fold(0) { acc, n -> acc + n } // OK
emptyList<Int>().reduce { acc, n -> acc + n }  // Exception

Best choice guide

  • Known non-empty + same result type -> reduce()
  • Might be empty -> fold()
  • Need custom start value -> fold()
  • Need different result type -> fold()

FAQ

What is the main difference between fold() and reduce() in Kotlin?

reduce() uses the first element as the initial accumulator, while fold() takes an explicit initial value that you provide.

When should I use fold() instead of reduce()?

Use fold() when the collection may be empty, when you need a custom starting value, or when the result type is different from the element type.

Does reduce() work on an empty list in Kotlin?

No. reduce() throws an exception on an empty collection because it has no first element to start with.

Can fold() and reduce() return the same result?

Yes. If you use fold() with an initial value that matches the natural starting value, both can produce the same final result.

Is fold() slower than reduce()?

In normal application code, the difference is usually not important. Choose based on correctness and readability first.

Can fold() return a different type in Kotlin?

Yes. That is one of its biggest advantages. For example, it can turn a list of Int into a String, Map, or custom object.

Should I always prefer fold()?

Not always, but often. It is usually safer and more flexible. reduce() is still a good choice when the collection is guaranteed to be non-empty and the first element is a natural starting value.

Take Quiz

Mini Project

Description

Create a small Kotlin program that processes a list of order amounts. This project demonstrates when fold() is more useful than reduce() by calculating totals safely and building a text summary from the same data.

Goal

Build a Kotlin script that sums order amounts and creates a readable summary string using fold().

Requirements

  • Create a list of integer order amounts
  • Use fold() to calculate the total amount starting from 0
  • Use fold() again to build a comma-separated summary string
  • Print both the total and the summary
  • Make sure the code still works even if the order list is empty
Take Quiz

Keep learning

Related questions

View more