Baeldung Pro – Scala – NPI EA (cat = Baeldung on Scala)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

1. Introduction

As a functional programming language, Scala provides powerful abstractions for working with collections, monads, and other structures. Among these abstractions, map() and flatMap() are two of the most frequently used higher-order functions. While they might seem similar at first glance, they serve different purposes and behave differently depending on the context.

In this tutorial, let’s explore the difference between those functions.

2. map()

The map() function transforms each collection element or monadic structures like Option, Future, etc. by applying a given function. The result is a new structure containing the transformed elements.

Let’s look at the syntax of the map() function:

def map[B](f: A => B): F[B]

As we can see, the map() function takes a function that transforms a value of type A into a value of type B.

Let’s explore a few example usages from Scala’s standard library types, such as List and Option:

// on List
val numbers = List(1,2,3,4,5)
val doubled = numbers.map(_ * 2) // List(2, 4, 6, 8, 10)
// on Option
val intOpt = Option(2)
val doubledOpt = intOpt.map(_ * 2) // Option(4)

Similarly, many classes from both the standard library and third-party libraries implement the map() function, as it’s one of the most commonly used operations.

Typically, map() is used when working with the successful side of a monadic structure, such as the Some case in Option or the Right case in Either. This allows transformations without altering the underlying structure. Failure cases such as None and Left are not transformed. However, this is more of a convention than a strict rule on the monadic implementation

3. flatMap()

The flatMap() function can also transform the elements of a collection or a monadic context. However, it’s applicable when the transformation function returns a structure of the same type. It flattens the resulting nested structure into a single structure.

Let’s look at the syntax:

def flatMap[B](f: A => F[B]): F[B]

Here, we can see that the transformation function f in flatMap() returns F[B], whereas in map(), it returns B.

Let’s look at an example by using List:

val sentences = List("Hello world", "Scala is awesome", "Functional programming is fun")
val words = sentences.flatMap(sentence => sentence.split(" "))
assert(words == List("Hello", "world", "Scala", "is", "awesome", "Functional", "programming", "is", "fun"))

In this example, the split method returns an array of words, and flatMap() is used to flatten the resulting lists of words into a single list. If we had used map() instead of flatMap(), the result would have been a nested list, where each element is a list of words for each sentence. To flatten this structure, we would then need to use flatten(), which can be avoided by directly using flatMap(), as it handles both the transformation and flattening in a single step.

Like map(), flatMap() also operates on the successful side of a monad.

When multiple nested flatMap() calls are involved, the code can become too difficult to read. In such cases, for-comprehension provides a more readable alternative. Internally, for-comprehensions are syntactic sugar for chaining flatMap() operations.

Let’s look at another example using Option chaining:

val supportedOs = Option("Linux")
val input = Option("Linux")
supportedOs.flatMap { os =>
  input.flatMap { in =>
    if (in == os) then Some("Succes") else None
  }
}

We can rewrite this to a simpler version using for-comprehension:

val supportedOs = Option("Linux")
val input = Option("Linux")
for {
  os <- supportedOs
  in <- input
  if in == os
} yield "Success"

This improves readability when dealing with multiple flatMap() chains.

4. Summary

Let’s summarize what we discussed about map and flatMap

Feature map() flatMap()
Transformation Applies a function that transforms a value to another value. Applies a function that transforms a value to a structure (e.g., a collection).
Syntax map(f: A => B): F[B] flatMap(f: A => F[B]): F[B]
Use Case Used when the function transforms a value without creating nested structures. Used when the function might create a nested structure or when you want to chain transformations that result in monads or other structures.

5. Conclusion

In this tutorial, we explored the differences between map() and flatMap() functions. These two are the most fundamental operations in any functional programming language. A clear understanding of these functions is essential for utilizing the full power of functional programming.