Scala map, flatMap, filter, and fold: Functional Operations
Scala map, flatMap, filter, and fold: Functional Operations
The functional collection operations are the bread and butter of Scala programming. Understanding map, flatMap, filter, and fold is essential — they replace most loops and enable clean, composable data transformations. This module covers each operation with practical examples.
map: Transform Every Element
map applies a function to every element and returns a new collection:
val numbers = List(1, 2, 3, 4, 5)
val doubled = numbers.map(_ * 2)
println(doubled) // List(2, 4, 6, 8, 10)
val strings = numbers.map(n => s"item-$n")
println(strings) // List(item-1, item-2, item-3, item-4, item-5)map preserves the structure — a List maps to a List, a Vector maps to a Vector.
filter: Keep Matching Elements
filter keeps only elements that satisfy a predicate:
val evens = numbers.filter(_ % 2 == 0)
println(evens) // List(2, 4)
val odds = numbers.filterNot(_ % 2 == 0)
println(odds) // List(1, 3, 5)flatMap: Map Then Flatten
flatMap applies a function that returns a collection for each element, then flattens the results:
val words = List("Hello World", "Scala Programming", "Functional Style")
val allWords = words.flatMap(_.split(" "))
println(allWords) // List(Hello, World, Scala, Programming, Functional, Style)Compare map vs flatMap:
words.map(_.split(" ").toList)
// List(List(Hello, World), List(Scala, Programming), List(Functional, Style))
words.flatMap(_.split(" ").toList)
// List(Hello, World, Scala, Programming, Functional, Style)flatMap is also the key to chaining Option and Either — it lets you write flat code instead of nested match expressions.
foldLeft: Aggregate from Left to Right
foldLeft accumulates a result by applying a function to each element and the running accumulator:
val sum = numbers.foldLeft(0)(_ + _)
println(sum) // 15
val product = numbers.foldLeft(1)(_ * _)
println(product) // 120
// Building a string from a list
val sentence = List("Scala", "is", "expressive").foldLeft("")(
(acc, word) => if (acc.isEmpty) word else s"$acc $word"
)
println(sentence) // Scala is expressiveThe signature is foldLeft[B](initial: B)(f: (B, A) => B): B. The first argument is the starting value; the function takes the accumulator and current element.
foldRight: Aggregate from Right to Left
foldRight works the same way but processes elements from right to left. This matters for non-associative operations:
val rightToLeft = numbers.foldRight(List.empty[Int])(_ :: _)
println(rightToLeft) // List(1, 2, 3, 4, 5) — preserves order
// foldLeft with :: would reverse the list:
val reversed = numbers.foldLeft(List.empty[Int])((acc, x) => x :: acc)
println(reversed) // List(5, 4, 3, 2, 1)reduce: foldLeft Without an Initial Value
reduce folds without an initial value — the first element is used as the starting accumulator:
val sum = numbers.reduce(_ + _) // 15
val max = numbers.reduce((a, b) => if (a > b) a else b) // 5reduce throws UnsupportedOperationException on an empty collection. Use reduceOption for safety, or foldLeft with a neutral element.
groupBy: Partition into a Map
groupBy splits a collection into a Map based on a key function:
case class Person(name: String, department: String)
val people = List(
Person("Alice", "Engineering"),
Person("Bob", "Marketing"),
Person("Carol", "Engineering"),
Person("Dave", "Marketing"),
Person("Eve", "Engineering")
)
val byDepartment = people.groupBy(_.department)
println(byDepartment("Engineering").map(_.name))
// List(Alice, Carol, Eve)partition: Split into Two Lists
partition splits a collection into a tuple of (matching, non-matching):
val (evens, odds) = numbers.partition(_ % 2 == 0)
println(evens) // List(2, 4)
println(odds) // List(1, 3, 5)zip and zipWithIndex
val names = List("Alice", "Bob", "Carol")
val ages = List(30, 25, 35)
val pairs = names.zip(ages)
println(pairs) // List((Alice,30), (Bob,25), (Carol,35))
val indexed = names.zipWithIndex
println(indexed) // List((Alice,0), (Bob,1), (Carol,2))Combining Operations
The real power comes from chaining operations:
case class Transaction(user: String, amount: Double, category: String)
val transactions = List(
Transaction("Alice", 50.0, "food"),
Transaction("Bob", 200.0, "electronics"),
Transaction("Alice", 30.0, "food"),
Transaction("Carol", 150.0, "electronics"),
Transaction("Bob", 10.0, "food"),
)
// Total spending per user, only for food category, users who spent > 20
val result = transactions
.filter(_.category == "food")
.groupBy(_.user)
.map { case (user, txns) => user -> txns.map(_.amount).sum }
.filter { case (_, total) => total > 20 }
println(result) // Map(Alice -> 80.0, Bob -> 10.0) — wait, Bob is 10 which fails filter
// Actually: Map(Alice -> 80.0)Frequently Asked Questions
Q: When should I use foldLeft vs reduce?
Use foldLeft when you have an initial value (like 0 for sum, 1 for product, or an empty collection for building). Use reduce only when the collection is guaranteed non-empty and the operation makes sense without an initial value (like finding the max). In practice, foldLeft is safer and more flexible.
Q: Is flatMap just map followed by flatten?
Yes — xs.flatMap(f) is equivalent to xs.map(f).flatten. The flatten operation takes a collection of collections and joins them into a single collection. Using flatMap directly is more efficient because it avoids creating the intermediate nested collection.
Q: Why does the order in foldLeft's function matter?
In foldLeft(initial)(f), the function f takes (accumulator, element). In foldRight, it takes (element, accumulator). Getting the order wrong is a common bug — for example, foldRight(List.empty[Int])(_ :: _) preserves order, but foldRight(List.empty[Int])((acc, x) => acc :: x) would be a type error since you'd be prepending a list to an int.
Part of Scala Mastery Course — Module 12 of 22.
