1. Overview

In this tutorial, we’re going to get familiar with the subtle differences between map() and flatMap() in Kotlin.

2. map()

map() is an extension function in Kotlin that is defined as:

fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> 

As shown above, this function iterates over all elements of an Iterable<T> one by one. During this iteration, it transforms every single element of type T to another element of type R. At the end, it converts all elements of the receiving collection, and we’ll end up with a List<R>.

This function is usually useful in one-to-one mapping situations. For example, let’s suppose each order consists of many order lines as its detailed purchase items:

class Order(val lines: List<OrderLine>)
class OrderLine(val name: String, val price: Int)

Now, if we have an Order, we can use map() to find the name of each item:

val order = Order(
  listOf(OrderLine("Tomato", 2), OrderLine("Garlic", 3), OrderLine("Chives", 2))
val names = order.lines.map { it.name }

assertThat(names).containsExactly("Tomato", "Garlic", "Chives")

Here, we’re converting a List<OrderLine> to a List<String> by passing a simple transformation function. This function accepts each OrderLine as the input (the it variable) and converts it to a String. As another example, we can calculate the total price of an Order like:

val totalPrice = order.lines.map { it.price }.sum()
assertEquals(7, totalPrice)

Basically, map() is the equivalent of the following imperative coding style:

val result = mutableListOf<R>()
for (each in this) {
    result += transform(each)

When we’re using map(), we just have to write the transform part. Defining a new collection, iteration, and adding each transformed element to that collection is just some boilerplate code and part of the implementation details.

3. flatMap()

As opposed to map(), flatMap() is usually useful for flattening one-to-many relationships. Because of that, its signature looks like:

fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R>

As shown above, it transforms each element of type T into a collection of type R. Despite this, instead of ending up with a List<Iterable<R>>, flatMap() flattens each Iterable<R> to its individual elements. Therefore, we’ll have a List<R> as a result.

As an example, let’s suppose we have a collection of orders, and we’re going to find all the distinct item names:

val orders = listOf(
  Order(listOf(OrderLine("Garlic", 1), OrderLine("Chives", 2))),
  Order(listOf(OrderLine("Tomato", 1), OrderLine("Garlic", 2))),
  Order(listOf(OrderLine("Potato", 1), OrderLine("Chives", 2))),

At first, we somehow should convert the List<Order> to a List<OrderLine>. If we use map() here, we’ll end up with a List<List<OrderLine>>, which is not desirable:

orders.map { it.lines } // List<List<OrderLine>>

Since we need to flatten the List<OrderLine> to individual OrderLines here, we can use the flatMap() function:

val lines: List<OrderLine> = orders.flatMap { it.lines }
val names = lines.map { it.name }.distinct()
assertThat(names).containsExactlyInAnyOrder("Garlic", "Chives", "Tomato", "Potato")

As shown above, the flatMap() function flattens the one-to-many relationship between each Order and its OrderLines.

The imperative style equivalent of flatMap() is something like:

val result = mutableListOf<OrderLine>()
for (order in orders) {
    val transformedList = order.lines
    for (individual in transformedList) {
        result += individual

Again, the collection initialization, iteration, and flattening are part of the hidden implementation details of flatMap(). All we have to do is provide the transformation function.

4. Conclusion

In this article, we learned the differences between map() and flatMap() in Kotlin. To sum up, map() is usually useful for one-to-one mappings, while flatMap() is more useful for flattening one-to-many mappings.

As usual, all the examples are available over on GitHub.

Comments are closed on this article!