What does Result.recover (and recoverCatching) do, and how is it the dual of map?
answer
- recover = dual of map, acts on the failure branch
- recover returns Result; getOrElse returns bare R
- recoverCatching catches throws from the recovery lambda
- Success passes through recover unchanged
- Chain recoverCatching for layered fallbacks
basics
~20 srecover turns a failed Result back into a success by running a lambda on the exception to produce a fallback value. Successes pass through untouched. recoverCatching is the same but catches exceptions thrown inside that recovery lambda.
solid answer
~40 srecover(transform: (Throwable) -> R): Result<R> is the failure-side mirror of map. On a failure it calls transform with the captured Throwable and re-wraps the returned value as a success; on a success it passes the value through unchanged. So map transforms the success branch, recover transforms the failure branch — both keep the result inside Result<R>. Unlike getOrElse, which exits the pipeline returning a bare R, recover stays in Result<R> so you can keep chaining (e.g. .recover { fallback() }.map { ... }). recoverCatching catches exceptions thrown by the recovery lambda and produces a new failure, exactly as mapCatching does for map. Use recover for fallback/degradation logic (cache miss -> default, primary source down -> secondary) when you want continued composition; use getOrElse when you're done and want the raw value.
code
kotlin · 5 linesfun config(): Result<Settings> =
loadRemoteConfig() // Result<Settings>
.recoverCatching { parseLocalFile() } // failure -> try local (may throw)
.recover { Settings.DEFAULTS } // still failing -> hard default
// recover never fails here (default is total), so config() is always successgo deeper
May know recover provides a fallback but is unlikely to articulate it as a Result-returning failure-branch transform.
States recover converts failures to successes and that recoverCatching guards the lambda.
Explains recover as the dual of map, distinguishes recover (Result) from getOrElse (bare R), and composes layered recoverCatching fallbacks.
Designs degradation/fallback strategies, reasons about which failures are recoverable, and handles cancellation correctly in coroutine pipelines.
## recover — transform the failure into a success value ```kotlin inline fun <R, T : R> Result<T>.recover(transform: (Throwable) -> R): Result<R> ``` - On a **failure**: calls `transform(exception)` and wraps the returned value as `Result.success`. - On a **success**: returns the value unchanged (re-typed to `Result<R>` since `T : R`). It is the precise **dual of `map`**: | Operator | Acts on | Leaves untouched | |----------|---------|------------------| | `map` | success value | failure | | `recover`| failure exception | success | ```kotlin val r: Result<Int> = runCatching { primarySource() } .recover { e -> cachedValueOrDefault() } // failure -> success(fallback) ``` ## recover vs getOrElse — the key distinction Both take `(Throwable) -> R` on the failure branch, but: - **`recover`** returns `Result<R>` — you stay in the pipeline and can keep chaining (`.map`, `.onSuccess`, another `.recover`). - **`getOrElse`** returns a bare `R` — it *terminates* the pipeline. ```kotlin val value: Int = runCatching { primary() } .recover { secondary() } // still Result<Int> .map { it + 1 } // can keep transforming .getOrElse { -1 } // now exit with a plain Int ``` ## recoverCatching — when recovery itself can throw ```kotlin inline fun <R, T : R> Result<T>.recoverCatching(transform: (Throwable) -> R): Result<R> ``` If the recovery `transform` throws, `recoverCatching` captures it as a new failure (the original failure is replaced by the new one), exactly as `mapCatching` does for `map`. Plain `recover` lets a throw from the recovery lambda escape. ## Composition pattern: layered fallbacks ```kotlin val data = loadFromNetwork() .recoverCatching { loadFromDisk() } // network failed -> try disk .recoverCatching { loadBundledDefault() } // disk failed -> bundled .getOrThrow() // give up and surface the last error ``` Each `recoverCatching` only runs if the prior stage is still a failure; a success short-circuits the rest. Note the same `Throwable`-catching caveat as `mapCatching`: in coroutines, re-throw `CancellationException` from inside the recovery lambda.
- How does recover differ from getOrElse when both take (Throwable) -> R?recover returns Result<R> so you can keep chaining; getOrElse returns a plain R and ends the pipeline.
- What happens to the original failure if the recover lambda also throws?With plain recover the new exception escapes the call. With recoverCatching it is captured as a new failure, replacing the original one.
map repaints the good parts; recover is the repair shop that fixes broken parts so they re-enter the line — and recoverCatching is a repair shop with its own reject bin.
saying these in an interview costs you the question
- Saying recover runs on a success (it only acts on failures)
- Confusing recover with getOrElse (recover stays in Result)
- Not knowing recoverCatching guards the recovery lambda
- Claiming recover keeps the result a failure
- Forgetting the CancellationException hazard in recoverCatching