skip to content

What does Result.fold do, and how does it differ from chaining getOrElse with onSuccess/onFailure?

level: middleimportance: must knowfreq 60%

answer

  1. fold = two lambdas, returns one value R
  2. Both fold branches return the same type
  3. onSuccess/onFailure = side effects, return same Result
  4. getOrElse == fold with identity onSuccess
  5. fold is total/exhaustive — no missed branch

basics

~20 s

fold 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 s

fold(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 lines
kotlin
sealed 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

for a junior

Knows fold takes onSuccess/onFailure and returns one of them; may not distinguish it cleanly from onSuccess/onFailure.

for a middle

Clearly separates value-producing fold from side-effecting onSuccess/onFailure and uses fold to build a UI/response state.

for a senior

Explains inline/non-local-return semantics, the common-supertype inference of R, and when fold beats map+getOrElse.

for a principal

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)

context