What does Result.fold do, and how does it differ from chaining getOrElse with onSuccess/onFailure?
answer
- fold = two lambdas, returns one value R
- Both fold branches return the same type
- onSuccess/onFailure = side effects, return same Result
- getOrElse == fold with identity onSuccess
- fold is total/exhaustive — no missed branch
basics
~20 sfold takes two lambdas — one for the success value and one for the failure exception — and returns a single result by running whichever branch matches. It collapses both outcomes into one value in one call.
solid answer
~40 sfold(onSuccess: (T) -> R, onFailure: (Throwable) -> R): R is the canonical way to fully consume a Result, mapping both branches to a common type R and returning it. It is exhaustive: exactly one of the two lambdas runs. onSuccess/onFailure differ fundamentally — they are side-effecting: each takes a lambda but returns the *same* Result (for chaining), invoking the action only on the matching branch and ignoring its return value. So fold produces a value; onSuccess/onFailure produce a Result. getOrElse only handles the failure branch (the success value passes through), so it's a special case of fold where onSuccess is identity. Use fold when you must turn both outcomes into the same type (e.g. an HTTP response or UI state); use onSuccess/onFailure for logging/metrics while keeping the Result flowing.
code
kotlin · 8 linessealed interface UiState
data class Loaded(val name: String) : UiState
data class Failed(val reason: String) : UiState
fun toUiState(r: Result<User>): UiState = r.fold(
onSuccess = { Loaded(it.name) },
onFailure = { Failed(it.message ?: "unknown") }
)go deeper
Knows fold takes onSuccess/onFailure and returns one of them; may not distinguish it cleanly from onSuccess/onFailure.
Clearly separates value-producing fold from side-effecting onSuccess/onFailure and uses fold to build a UI/response state.
Explains inline/non-local-return semantics, the common-supertype inference of R, and when fold beats map+getOrElse.
Treats fold as the exhaustiveness guarantee at a boundary and weighs returning a sealed state vs propagating Result up the stack.
## fold — the exhaustive consumer `fold` is the most complete way to consume a `Result<T>`: ```kotlin inline fun <R, T> Result<T>.fold( onSuccess: (value: T) -> R, onFailure: (exception: Throwable) -> R ): R ``` Exactly one branch runs, and **both must return the same type `R`**, so `fold` always yields a single non-Result value. It's the idiomatic way to convert a Result into something else (a sealed UI state, an HTTP status, a default-bearing object): ```kotlin val message: String = runCatching { loadUser() }.fold( onSuccess = { user -> "Hello, ${user.name}" }, onFailure = { e -> "Error: ${e.message}" } ) ``` ## onSuccess / onFailure — side-effects that return the same Result ```kotlin inline fun <T> Result<T>.onSuccess(action: (value: T) -> Unit): Result<T> inline fun <T> Result<T>.onFailure(action: (exception: Throwable) -> Unit): Result<T> ``` These are **for side effects only** — the `action`'s return value is discarded and they hand back the *original, unchanged* `Result<T>` so you can chain. Use them for logging, metrics, or analytics without altering the value: ```kotlin runCatching { call() } .onSuccess { log.info("ok") } .onFailure { log.error("failed", it) } .getOrNull() ``` ## How they relate - **fold**: handles *both* branches, returns a derived value `R`. Exhaustive. - **getOrElse**: handles only failure; success value passes through unchanged. Equivalent to `fold({ it }, onFailure)`. - **onSuccess/onFailure**: handle one branch each *for effect*, return the same `Result` — pipeline plumbing, not a final value. ## Why prefer fold over manual if/else Manual `if (r.isSuccess) r.getOrThrow() else handle(r.exceptionOrNull()!!)` forces a non-null assertion and risks forgetting a branch. `fold` is total and exception-safe by construction. All of these are `inline`, so lambdas are not allocated and can perform non-local returns.
- What does onSuccess return, and why is that useful?It returns the original Result<T> unchanged, which lets you chain further operators (onFailure, map, getOrNull) after a side effect like logging.
- Can the two fold lambdas return different types?No. Both must return the common type R; the compiler infers R as their least upper bound, and the whole expression has that single type.
fold is a fork in the road that merges back into one path; onSuccess/onFailure are roadside cameras that snap a photo and let the car drive on unchanged.
saying these in an interview costs you the question
- Saying fold returns a Result (it returns the merged value R)
- Thinking onSuccess transforms the value like map does
- Believing both fold branches can run
- Using isSuccess + getOrThrow + getOrNull(!!) instead of fold
- Claiming onFailure swallows and resolves the failure (it leaves the Result a failure)