## 1. Introduction

When working with functional programming languages like Scala, it’s common to have a variety of high-order functions to process collections, including *reduceLeft*, *reduceRight*, *foldLeft*, *foldRight*, *scanLeft*, and *scanRight. *Each of these functions combines the elements of a collection into a final result. However, they differ in how they iterate through the collection and how they compile the final result.

In this quick tutorial, we’ll learn about these six important functions of Scala. We will compare and contrast their different behaviors and provide some code examples to illustrate their use cases.

## 2. *reduceLeft *and *reduceRight*

The *reduceLeft* combines the elements of a collection by successively applying a binary operator from left to right and produces a single result. It applies the binary operation to the first two elements, then applies the outcome to the third element, and so on. It reduces the collection to a single value:

```
"reduceLeft" should "calculate the sum correctly" in {
val numbers = List(1, 2, 3, 4, 5)
val actualSum = numbers.reduceLeft(_ + _)
// op: 1 + 2 = 3
// op: 3 + 3 = 6
// op: 6 + 4 = 10
// op: 10 + 5 = 15
// res: Int = 15
assert(15 == actualSum)
}
```

Similarly, we can find the largest number or concatenate a *List* of *Strings*:

```
"reduceLeft" should "find the largest number correctly" in {
val numbers = List(10, 22, 5, 71, 43)
val actualResult = numbers.reduceLeft(_ max _)
assert(71 == actualResult)
}
```

```
"reduceLeft" should "concatenate strings correctly" in {
val alphabets = List("A", "B", "C", "D", "E")
val actualResult = alphabets.reduceLeft(_ + _)
assert("ABCDE" == actualResult)
}
```

The *reduceRight* function operates in the same manner as *reduceLeft*, with the only difference being the direction. The *reduceRight* will apply the binary operation to the last two elements of the collection first, then the third-to-last two elements, and so on, until it reaches the first two elements:

```
"reduceRight" should "calculate the sum correctly" in {
val numbers = List(1, 2, 3, 4, 5)
val actualSum = numbers.reduceRight(_ + _)
// op: 5 + 4 = 9
// op: 9 + 3 = 12
// op: 12 + 2 = 14
// op: 14 + 1 = 15
// res: Int = 15
assert(15 == actualSum)
}
```

**When the input collection is empty, reduceLeft and reduceRight throw an UnsupportedOperationException because there is nothing to reduce.**

Here’s some code that illustrates the behavior:

```
"reduceLeft" should "throw an exception" in {
val numbers = List.empty[Int]
assertThrows[UnsupportedOperationException] {
numbers.reduceLeft(_ max _)
}
}
```

## 3. *foldLeft* and *foldRight*

**The foldLeft and foldRight are similar to reduceLeft and reduceRight, respectively, but they take an initial value as a parameter. **

In the *foldLeft*, the initial value will combine with the first element of the collection using a binary operator, and the result will combine with the second element, and so on:

```
"foldLeft" should "calculate the sum correctly" in {
val numbers = List(1, 2, 3, 4, 5)
val actualSum = numbers.foldLeft(5)(_ + _)
// op: 5 + 1 = 6
// op: 6 + 2 = 8
// op: 8 + 3 = 11
// op: 11 + 4 = 15
// op: 15 + 5 = 20
// res: Int = 20
assert(20 == actualSum)
}
```

Similarly, in the *foldRight, *the initial value will combine with the last element, and the result will combine with the second-last element, and so on, until it reaches the first element:

```
"foldRight" should "concatenate the strings correctly" in {
val alphabets = List("A", "B", "C", "D", "E")
val actualResult = alphabets.foldRight("$")(_ + _)
// op: E + $ = E$
// op: D + E$ = DE$
// op: C + DE$ = CDE$
// op: B + CDE$ = BCDE$
// op: A + BCDE$ = ABCDE$
// res: String = ABCDE$
assert("ABCDE$" == actualResult)
}
```

When the input collection is empty, the *foldLeft* and *foldRight *functions return a collection with only the initial element:

```
"foldRight" should "return the initial element i.e $" in {
val alphabets = List.empty[String]
val actualResult = alphabets.foldRight("$")(_ + _)
assert("$" == actualResult)
}
```

## 4. *scanLeft* and *scanRight*

**The scanLeft and scanRight are similar to foldLeft and foldRight, respectively, but they return a collection of intermediate results, not just the final result**:

```
"scanLeft" should "have correct intermediate states" in {
val numbers = List(1, 2, 3, 4, 5)
val actualResult = numbers.scanLeft(1)(_ + _)
assert(List(1, 2, 4, 7, 11, 16) == actualResult)
}
"scanRight" should "have correct intermediate states" in {
val numbers = List(1, 2, 3, 4, 5)
val actualResult = numbers.scanRight(1)(_ + _)
assert(List(16, 15, 13, 10, 6, 1) == actualResult)
}
```

When the input collection is empty, the *scanLeft* and *scanRight *functions return a collection with only the initial element:

```
"scanRight" should "return the initial element i.e 5" in {
val numbers = List.empty[Int]
val actualResult = numbers.scanRight(5)(_ + _)
assert(List(5) == actualResult)
}
```

## 5. When to Use Each

What function should be chosen over the other totally depends on the context. We can choose the ideal function for our needs by understanding their distinct behaviors.

Therefore, the following table can help us to find the most suitable function according to our requirements:

Function | Output a Single Value | Output Intermediate Results | Accepts Initial Value | When Collection is Empty | Examples |
---|---|---|---|---|---|

reduceLeft |
Yes | No | No | throws UnsupportedOperationException |
Find max/min in the list |

reduceRight |
Yes | No | No | throws UnsupportedOperationException |
String concatenation |

foldLeft |
Yes | No | Yes | returns a collection with only the initial element | Tree traversal |

foldRight |
Yes | No | Yes | returns a collection with only the initial element | File system traversal |

scanLeft |
No | Yes | Yes | returns a collection with only the initial element | Inventory management |

scanRight |
No | Yes | Yes | returns a collection with only the initial element | Statistical analysis |

## 6. Conclusion

In this tutorial, we have learned about the six powerful functions of Scala language for aggregating the elements of a collection. These functions use different traversal orders and accumulation strategies to aggregate elements. By understanding the differences between these functions, we can choose the right one for our needs.

As usual, the full source code can be found over on GitHub.