What is the difference between Result.map and Result.mapCatching, and when must you choose mapCatching?
answer
- map = transform, lambda exceptions ESCAPE
- mapCatching = transform, lambda exceptions become failures
- Both skip the lambda on an existing failure
- Pure transform -> map; fallible transform -> mapCatching
- mapCatching catches Throwable incl. CancellationException
basics
~20 sBoth transform the success value with your lambda and leave failures alone. The difference: if your lambda itself throws, map lets the exception escape, while mapCatching catches it and turns the Result into a failure.
solid answer
~40 smap(transform: (T) -> R): Result<R> applies transform on success and passes failures through untouched. Crucially, if transform throws, map does NOT catch it — the exception propagates out of the call, escaping the Result abstraction. mapCatching(transform: (T) -> R): Result<R> behaves identically on the happy path but wraps the transform in a runCatching-style guard, so an exception thrown inside transform is captured as a new failure Result instead of propagating. Choose mapCatching whenever the transform can itself fail (parsing, I/O, conversions) and you want that failure to stay inside the Result pipeline. Use plain map when transform is total/pure (a field access, arithmetic) — it's cheaper conceptually and signals 'this can't throw'. Both short-circuit: on an already-failed Result, neither runs the lambda; the original failure flows through.
code
kotlin · 6 linesval raw: Result<String> = Result.success("42")
val viaMap: Result<Int> = raw.map { it.toInt() } // success(42)
val bad: Result<String> = Result.success("oops")
// bad.map { it.toInt() } // throws — escapes the Result
val safe: Result<Int> = bad.mapCatching { it.toInt() } // failure(NumberFormatException)go deeper
Knows map transforms the value and leaves failures alone; may not realize map doesn't catch lambda exceptions.
Correctly states that only mapCatching captures exceptions from the transform and picks the right one per transform's fallibility.
Raises the catch-Throwable / CancellationException hazard and reasons about map for pure transforms as a clarity signal.
Designs pipeline conventions (where to use mapCatching, how to rethrow cancellation) and considers whether Result is even the right abstraction vs a typed error.
## map — transform the success value ```kotlin inline fun <R, T> Result<T>.map(transform: (T) -> R): Result<R> ``` If the Result is a **success**, `map` applies `transform` and re-wraps the new value as a success of type `R`. If it's a **failure**, `transform` is skipped and the original failure is returned as `Result<R>`. **The trap:** `map` does *not* protect against `transform` throwing. If your lambda throws, the exception propagates out of `map` — it does **not** become a failed Result: ```kotlin val r: Result<Int> = Result.success("x") .map { it.toInt() } // throws NumberFormatException — escapes! ``` ## mapCatching — transform AND capture lambda failures ```kotlin inline fun <R, T> Result<T>.mapCatching(transform: (T) -> R): Result<R> ``` Same happy path, but `transform` runs inside an internal catch. If it throws, the result becomes `Result.failure(thatException)` instead of propagating: ```kotlin val r: Result<Int> = Result.success("x") .mapCatching { it.toInt() } // Result.failure(NumberFormatException) ``` ## Choosing between them - **map** when `transform` is **total/pure** (cannot throw): `.map { it.id }`, `.map { it * 2 }`. - **mapCatching** when `transform` **can fail** (parse, decode, network, conversion) and you want the failure folded into the Result. ## Short-circuiting on failure Both skip the lambda when the receiver is already a failure — the original exception is preserved and re-typed to `Result<R>`. This is the standard "propagate the first error" behavior of a Result pipeline. ## A caution about catching everything `mapCatching` (like `runCatching`) catches `Throwable`, including things like `CancellationException` in coroutines and arguably fatal errors (`OutOfMemoryError`). In coroutine code, catching `CancellationException` breaks structured concurrency, so guard against swallowing it (re-throw it explicitly) when using `mapCatching` inside suspend functions.
- If the Result is already a failure, does map run its transform lambda?No. map (and mapCatching) skip the transform on a failure and pass the original exception through, re-typed to Result<R>.
- Why can mapCatching be dangerous inside a suspend function?It catches Throwable, including CancellationException. Swallowing that breaks coroutine cancellation/structured concurrency, so you should re-throw CancellationException.
map is a conveyor belt with no safety net — drop a part and it hits the floor; mapCatching has a net that drops the part into a 'rejects' bin (a failure Result).
saying these in an interview costs you the question
- Claiming map catches exceptions thrown by its transform
- Saying mapCatching runs the transform even on an existing failure
- Not knowing mapCatching catches all Throwable (cancellation risk)
- Confusing map (transforms value) with onSuccess (side effect only)
- Thinking map changes a failure into a success