skip to content

Explain flatMap and flatten in Kotlin. How do they differ, and when would you reach for each?

level: middleimportance: must knowfreq 80%

answer

  1. flatten: List<List<T>> -> List<T>
  2. flatMap = map (to iterable) + flatten, one pass
  3. flatMap is 1-to-many; map is 1-to-1
  4. flatMap { f } == map { f }.flatten()
  5. lazy fusion on Sequence; flatMapTo/flattenTo for destinations

basics

~20 s

flatten takes a list of lists and merges them into one flat list. flatMap first turns each element into a list (or any iterable) and then merges all those into one flat list. Use flatten when you already have nested lists; flatMap when you produce the nested lists yourself.

solid answer

~40 s

flatten() is an extension on Iterable<Iterable<T>> that concatenates the inner iterables into a single List<T>. flatMap takes a transform `(T) -> Iterable<R>`, applies it to each element, and concatenates all produced iterables into one List<R>; it is conceptually `map { transform(it) }.flatten()` but in one pass. Reach for flatten when nesting already exists; reach for flatMap when the transform itself yields a collection per element (one-to-many) — e.g. expanding each order into its line items. Unlike map (1-to-1), flatMap is one-to-many, so the result size is the sum of the produced iterables' sizes (can be larger, smaller, or empty). There is also flatMapIndexed, and the *To variants flatMapTo/flattenTo. On sequences, flatMap fuses lazily without materialising every inner collection at once.

code

kotlin · 5 lines
kotlin
data class Order(val items: List<String>)
val orders = listOf(Order(listOf("a", "b")), Order(listOf("c")))
val all = orders.flatMap { it.items }   // [a, b, c]
// equivalent but with an intermediate List<List<String>>:
val all2 = orders.map { it.items }.flatten()

go deeper

for a junior

Knows flatten merges nested lists and flatMap produces then merges per-element collections.

for a middle

Explains the one-to-many nature, the map+flatten equivalence, output-size rule, and when to pick flatten vs flatMap.

for a senior

Discusses single-pass vs intermediate allocation, lazy fusion on sequences, and flatMapTo/flatMapIndexed variants.

for a principal

Reasons about memory profile of nested expansion at scale and chooses eager vs lazy flatMap to bound intermediate materialisation.

## flatten `flatten()` is an extension on `Iterable<Iterable<T>>` (so a `List<List<T>>`, `List<Set<T>>`, etc.). It **concatenates** the inner iterables in order into one `List<T>`. ```kotlin val nested = listOf(listOf(1, 2), listOf(3), listOf<Int>(), listOf(4, 5)) val flat = nested.flatten() // [1, 2, 3, 4, 5] ``` Empty inner lists simply contribute nothing. ## flatMap `flatMap` combines **transform + flatten**. Its transform returns an `Iterable<R>` (one-to-many) per element, and all results are concatenated: ```kotlin public inline fun <T, R> Iterable<T>.flatMap( transform: (T) -> Iterable<R> ): List<R> ``` ```kotlin val orders = listOf( listOf("apple", "pear"), // order 0 items listOf("milk") // order 1 items ) val allItems = orders.flatMap { it } // [apple, pear, milk] val ranges = listOf(1, 3).flatMap { 1..it } // [1, 1, 2, 3] ``` So `xs.flatMap(f)` is equivalent to `xs.map(f).flatten()` but does it in a single pass without the intermediate `List<List<R>>`. ## map vs flatMap - `map`: `(T) -> R`, strictly **1-to-1**, output size == input size. - `flatMap`: `(T) -> Iterable<R>`, **1-to-many**, output size = sum of inner sizes (may grow, shrink, or be empty). If each element naturally expands into zero-or-more results, `flatMap` is the tool; if you accidentally use `map` you get a `List<List<R>>` you then have to flatten. ## When to pick which - Already have `List<List<T>>` → `flatten()`. - Need to **produce** a collection per element then merge → `flatMap`. - Producing **nullable singles** (not collections) → use `mapNotNull`, not flatMap. ## Related variants - `flatMapIndexed { index, value -> ... }` - `flatMapTo(dest) { ... }`, `flattenTo(dest)` — write into an existing mutable collection. - On `Sequence`, `flatMap` is lazy and fuses through the chain, avoiding materialising all inner collections at once. ## Gotcha The transform must return an `Iterable` (or `Sequence`/`Array` overload). Returning a single element by mistake won't compile; wrap it with `listOf(...)` if you really mean one element.

  • What is the size relationship between input and output for flatMap?
    Output size equals the sum of the sizes of the iterables the transform returns. It can be larger than the input (many per element), smaller, or even zero if every transform returns an empty iterable.
  • How does flatMap behave differently on a Sequence vs a List?
    On a List it eagerly builds the full result list. On a Sequence it is lazy and fuses with surrounding operators, pulling inner elements on demand without materialising every inner collection at once.

flatten is emptying several boxes onto one table; flatMap is opening each box, unpacking its contents into a new pile, and pouring them all onto the same table.

saying these in an interview costs you the question

  • Saying flatMap is 1-to-1 like map
  • Claiming flatMap can transform but not flatten (or vice versa)
  • Thinking flatten works on a non-nested list
  • Using map then complaining about List<List<T>> instead of flatMap
  • Believing output size always equals input size

context