skip to content

Explain `Result<T>` in the Kotlin stdlib: how it represents success/failure, how to create and consume it, and its key limitations.

level: middleimportance: should knowfreq 60%

answer

  1. inline value class: value or Throwable
  2. runCatching / success / failure
  3. fold, getOrElse, map vs mapCatching
  4. runCatching swallows CancellationException
  5. error channel is untyped Throwable

basics

~10 s

Result<T> holds either a successful value or a thrown error, without using exceptions to control flow. You can check which one it is and transform the value safely.

solid answer

~40 s

`Result<T>` is an inline value class wrapping either a success value or a `Throwable`. Create it with `Result.success(v)`, `Result.failure(e)`, or `runCatching { ... }`, which catches exceptions and wraps them. Consume it with `getOrNull()`, `getOrDefault()`, `getOrElse { }`, `getOrThrow()`, `exceptionOrNull()`, or `fold(onSuccess, onFailure)`. Transform with `map`, `mapCatching`, `recover`, and side-effect with `onSuccess`/`onFailure`. Because it is a `@JvmInline value class`, a successful result avoids boxing in many cases. Key limitations: by convention you should not use `Result` as a function return type or parameter pre-2.x (the compiler historically restricted it; `kotlin.Result` is best for local/internal flows), `runCatching` catches *all* `Throwable` including `CancellationException`, which is dangerous in coroutines, and it carries no typed error channel — the error is just `Throwable`.

code

kotlin · 3 lines
kotlin
val parsed: Int = runCatching { input.toInt() }
    .onFailure { log.warn("bad input", it) }
    .getOrDefault(0)

go deeper

for a junior

Knows Result holds success-or-failure and you can get the value or a default.

for a middle

Creates with runCatching, consumes with fold/getOrElse, and names map/recover.

for a senior

Explains the inline value class, mapCatching vs map, and the CancellationException pitfall.

for a principal

Weighs Result vs sealed/Either for API contracts, knows the public-return-type restriction, and reasons about error-channel typing trade-offs.

## What `Result<T>` is `kotlin.Result<T>` is a stdlib type that holds **one of two outcomes**: a **success** carrying a value of type `T`, or a **failure** carrying a `Throwable`. It lets you treat errors as ordinary return data (a 'railway' / monadic style) instead of unwinding the stack with exceptions. It is declared as a `@JvmInline value class` wrapping an `Any?`. Internally a failure is stored as a small `Failure` holder containing the exception, and a success stores the raw value. Being an inline value class means successes often avoid heap allocation/boxing. ## Creating a Result ```kotlin val a = Result.success(42) val b = Result.failure<Int>(IllegalStateException("boom")) val c = runCatching { riskyParse(input) } // catches throwables, wraps them ``` `runCatching { }` is the workhorse: it runs the block and returns `Result.success` or, if anything is thrown, `Result.failure`. There is also a receiver form `x.runCatching { ... }`. ## Inspecting / consuming - `isSuccess` / `isFailure` — booleans. - `getOrNull()` — value or `null`. - `exceptionOrNull()` — the throwable or `null`. - `getOrThrow()` — value or rethrows the stored exception. - `getOrDefault(d)` / `getOrElse { e -> ... }` — fallback on failure. - `fold(onSuccess = { }, onFailure = { })` — handle both branches and return a value. ## Transforming - `map { }` — transform the success value (does **not** catch exceptions in the transform). - `mapCatching { }` — like `map` but catches exceptions thrown by the transform into a failure. - `recover { e -> ... }` / `recoverCatching` — turn a failure back into a success. - `onSuccess { }` / `onFailure { }` — side effects, return the same `Result`. ```kotlin val len: Int = runCatching { fetch() } .map { it.body } .recover { "" } .getOrThrow() .length ``` ## Important limitations / gotchas - **Coroutines**: `runCatching` catches **all** `Throwable`, including `kotlinx.coroutines.CancellationException`. Swallowing cancellation breaks structured concurrency, so inside coroutines prefer explicit `try/catch` that rethrows `CancellationException`. - **No typed error**: the failure channel is just `Throwable`; it cannot express a closed set of domain errors like a `sealed class` can. For rich domain modeling, a `sealed`/`Either`-style type is often better. - **API design**: `kotlin.Result` was historically restricted as a public function return type (compiler-enforced), so it is most idiomatic for local/internal composition rather than as a published API contract. - `map` vs `mapCatching`: `map` lets exceptions from the lambda escape; `mapCatching` re-wraps them. ## Why it fits the 'stdlib model' theme Kotlin maps the cross-language concept of an **error-as-value monad** onto a single inline class plus a family of extension functions, instead of a language feature. This keeps exceptions available while offering a functional alternative when you want one.

  • Why is runCatching dangerous inside a coroutine?
    It catches CancellationException, swallowing cancellation and breaking structured concurrency; rethrow it or use targeted try/catch.
  • What's the difference between map and mapCatching?
    map lets exceptions from the transform propagate; mapCatching wraps them into a Result.failure.

saying these in an interview costs you the question

  • Saying Result can hold a typed domain error instead of just Throwable
  • Using runCatching in coroutine code without handling CancellationException
  • Confusing map (no catch) with mapCatching (catches)
  • Claiming getOrThrow returns null on failure
  • Treating Result as a free replacement for sealed-class domain errors

context