skip to content

Transformations

map, filter, and flatMap plus their indexed, not-null, and typed variants cover most element-level reshaping. The detail worth knowing is that each step allocates a new list, which is the setup for the Sequence discussion.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

6

What do the standard library functions map and filter do on a Kotlin collection, and what does each return?

level: juniorimportance: must knowfreq 92%

answer

  1. map = 1-to-1 transform, same size
  2. filter = keep where predicate true, size shrinks
  3. filterNot = inverse of filter
  4. both return a NEW List, source untouched
  5. eager on Iterable, lazy via asSequence()

basics

~20 s

map turns each element into something else and gives back a new list of the results. filter keeps only the elements that match a condition and gives back a new list. Both leave the original list unchanged.

solid answer

~40 s

map applies a transform lambda to every element and returns a new List with the transformed values, preserving order and size. filter applies a predicate (a function returning Boolean) and returns a new List containing only elements for which it is true; size may shrink. Both are extension functions on Iterable, are non-mutating (the receiver is untouched), and eagerly produce a fresh ArrayList. The lambda's last expression is the result for map; filterNot is the inverse of filter. For nullable results use mapNotNull; for an index use mapIndexed. On a List the static return type is List<R>, but the concrete object is a new ArrayList.

code

kotlin · 4 lines
kotlin
val xs = listOf(1, 2, 3, 4, 5)
val doubledEvens = xs.filter { it % 2 == 0 }.map { it * 2 }  // [4, 8]
println(xs)            // [1, 2, 3, 4, 5] -- unchanged
println(doubledEvens)  // [4, 8]

go deeper

for a junior

States map transforms each element and filter keeps matching ones, both returning a new list without mutating the source.

for a middle

Adds that results are eager ArrayLists, size rules (map 1-to-1, filter shrinks), and mentions filterNot plus asSequence for laziness.

for a senior

Discusses intermediate-list allocation in chains, read-only return type vs concrete ArrayList, and when to switch to sequences.

for a principal

Frames eager-vs-lazy trade-offs at scale, API design of returning read-only List interfaces, and predictable allocation behaviour for hot paths.

## What these are `map` and `filter` are **extension functions** on `Iterable<T>` (and `Array`, `Sequence`, `Map`, etc.) from the Kotlin standard library. An *extension function* is a function you call with dot-syntax on a receiver as if it were a member. Both are **transformation operators**: they read the source collection and build a brand-new collection. Neither mutates the receiver. ## map `map` takes a **transform lambda** `(T) -> R` and returns a `List<R>` whose i-th element is the transform applied to the i-th source element. Order and **size are preserved** (N in, N out). ```kotlin val names = listOf("ann", "bob") val lengths: List<Int> = names.map { it.length } // [3, 3] ``` Here `it` is the implicit single parameter and the lambda's **last expression** is the produced value. ## filter `filter` takes a **predicate** `(T) -> Boolean` and returns a `List<T>` containing only elements for which the predicate is `true`. Size may shrink (0..N out); element type is unchanged. ```kotlin val evens = listOf(1, 2, 3, 4).filter { it % 2 == 0 } // [2, 4] ``` `filterNot { ... }` keeps elements where the predicate is **false** (the inverse). ## Key properties - **Eager**: on a `List`/`Iterable` the result is computed immediately into a new `ArrayList`. (Wrapping with `.asSequence()` makes it lazy.) - **Non-mutating**: the source is never changed; you get a new object. - **Return type**: declared as `List<R>`/`List<T>` (read-only interface), backed by a mutable `ArrayList` instance. - Chaining `xs.filter { ... }.map { ... }` creates an **intermediate list** for each step. ## Related operators in this family `mapNotNull` (drops nulls the transform returns), `mapIndexed` (gives `(index, value)`), `filterNotNull`, `filterIsInstance<T>()`, and `flatMap`.

  • If you chain filter then map on a large list, how many intermediate collections are created and how do you avoid them?
    Each eager step allocates one new list, so filter-then-map creates one intermediate list plus the final list. Use asSequence() to fuse the operations and avoid intermediates.
  • Does map ever change the collection's size?
    No. map is strictly 1-to-1, so output size equals input size. To change size you need filter, mapNotNull, or flatMap.

map is repainting every car a new colour; filter is a bouncer letting only some cars through. Neither alters the original lot.

saying these in an interview costs you the question

  • Saying map or filter mutates the original list
  • Claiming map can change the number of elements
  • Thinking the result is the same object / aliased to the source
  • Saying map and filter are lazy on a plain List (they are eager)
  • Confusing filter (keeps true) with filterNot (keeps false)

context

open as a page

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

level: middleimportance: must knowfreq 80%

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.

open as a page

How does mapIndexed work, and how does it relate to withIndex() and mapIndexedNotNull?

level: juniorimportance: should knowfreq 55%

basics

~10 s

mapIndexed transforms each element while also giving you its position number, starting at 0. So you can use both the index and the value to build each result.

open as a page

When would you use mapNotNull instead of map, and how does it differ from map followed by filterNotNull?

level: middleimportance: should knowfreq 78%

basics

~20 s

mapNotNull transforms each element but throws away any results that come out null. It is handy when a step can fail or skip values, letting you map and drop nulls in one pass instead of two.

open as a page

What does filterIsInstance do, and how does it differ from filter { it is T } plus a manual cast? Why does it need a reified type?

level: seniorimportance: should knowfreq 58%

basics

~20 s

filterIsInstance keeps only the elements that are of a given type and gives you back a list already typed as that type, so you do not have to cast each one yourself. It needs the type known at compile time to do this safely.

open as a page

What are the *To variants (mapTo, filterTo, flatMapTo, mapNotNullTo, etc.) and when would you choose them over the plain operators?

level: seniorimportance: nice to knowfreq 40%

basics

~20 s

The *To versions do the same transform but write the results into a collection you supply, instead of creating a brand-new list. You use them when you want to control the destination type or gather results from several sources into one collection.

open as a page