skip to content

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

level: middleimportance: should knowfreq 78%

answer

  1. mapNotNull = map + drop nulls in one pass
  2. transform returns R?, result is List<R> (non-null)
  3. great with toIntOrNull / nullable lookups / safe casts
  4. one allocation vs map+filterNotNull's two
  5. mapIndexedNotNull for the indexed variant

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.

solid answer

~40 s

mapNotNull takes a transform `(T) -> R?` and returns `List<R>` (non-null element type) containing only the non-null results. It both transforms and discards nulls in a single iteration. `map { ... }.filterNotNull()` is functionally equivalent but creates an extra intermediate list and iterates twice eagerly. mapNotNull shines when the transform itself produces optional values, e.g. parsing (`it.toIntOrNull()`), nullable lookups, or safe casts. The result's static type drops nullability (`R`, not `R?`), so downstream code needs no further null handling. There is also mapIndexedNotNull for the indexed variant. Internally mapNotNull uses mapNotNullTo with a fresh ArrayList; only adds a value when the transform returns non-null.

code

kotlin · 3 lines
kotlin
data class User(val id: Int, val email: String?)
val users = listOf(User(1, "[email protected]"), User(2, null), User(3, "[email protected]"))
val emails: List<String> = users.mapNotNull { it.email }  // ["[email protected]", "[email protected]"]

go deeper

for a junior

Knows mapNotNull transforms and removes null results, useful with toIntOrNull.

for a middle

Explains the R : Any non-null result type, the single-pass equivalence to map+filterNotNull, and common use cases like parsing.

for a senior

Reasons about allocation/iteration differences, the silent-drop hazard, and when explicit error handling beats dropping nulls.

for a principal

Weighs data-loss risk in pipelines, observability of dropped elements, and chooses mapNotNull vs partition/Either-style handling for correctness.

## The problem it solves Sometimes a transform can legitimately produce **no value** for some inputs — a parse fails, a map lookup misses, a safe cast doesn't apply. `mapNotNull` lets you express "transform each element, and if the transform yields `null`, drop it" in one operator. ## Signature and behaviour ```kotlin public inline fun <T, R : Any> Iterable<T>.mapNotNull( transform: (T) -> R? ): List<R> ``` - The transform returns a **nullable** `R?`. - The result element type is the **non-null** `R` (note the `R : Any` upper bound — `Any` is non-nullable). - Only non-null results are added, so downstream you never deal with nulls again. ```kotlin val raw = listOf("1", "x", "3") val nums: List<Int> = raw.mapNotNull { it.toIntOrNull() } // [1, 3] ``` `toIntOrNull()` returns `Int?`; the `"x"` produces `null` and is dropped. ## vs map + filterNotNull ```kotlin val a = raw.mapNotNull { it.toIntOrNull() } val b = raw.map { it.toIntOrNull() }.filterNotNull() ``` Both give `[1, 3]`, but: - `mapNotNull` does **one eager pass** and allocates **one** result list. - `map { ... }.filterNotNull()` builds an **intermediate** `List<Int?>` first, then a second list — two passes, two allocations. So prefer `mapNotNull` for clarity and fewer allocations. (On a `Sequence` the two are closer, since sequence ops fuse lazily, but `mapNotNull` is still the idiomatic single operator.) ## Related operators - `mapIndexedNotNull { index, value -> ... }` — indexed version. - `filterNotNull()` — just drops nulls without transforming. - `mapNotNullTo(destination) { ... }` — writes results into an existing mutable collection. ## Gotcha Returning `null` from the lambda is the *signal* to drop — make sure that's intended. If you `return@mapNotNull null` by accident (e.g. forgot a branch), elements silently vanish.

  • Why is the type parameter R bounded as R : Any in mapNotNull?
    The R : Any upper bound forces R to be non-nullable, so the returned List<R> is guaranteed free of nulls — the nullability lives only in the transform's R? return, not in the result element type.
  • Is there a risk of silently dropping data with mapNotNull?
    Yes. Any element whose transform returns null is removed without trace. If a null actually indicates an error you care about, mapNotNull will swallow it; prefer explicit handling (e.g. partition or logging) in that case.

saying these in an interview costs you the question

  • Saying mapNotNull only filters and does not transform
  • Claiming the result type stays nullable (List<R?>)
  • Believing map+filterNotNull is more efficient than mapNotNull
  • Not recognising that null from the lambda means 'drop this element'
  • Confusing mapNotNull with filterNotNull (which does no transform)

context