skip to content

Given a Kotlin Result<T>, what are the differences between getOrNull(), getOrDefault(), getOrElse(), and getOrThrow() for extracting the value?

level: juniorimportance: must knowfreq 70%

answer

  1. Null / Default / Else / Throw
  2. getOrDefault = eager arg; getOrElse = lazy lambda + sees exception
  3. getOrThrow re-throws the stored Throwable
  4. exceptionOrNull mirrors getOrNull
  5. Prefer getOrElse for expensive fallbacks

basics

~10 s

All four pull the success value out of a Result. getOrNull returns null on failure, getOrDefault returns a fallback you pass, getOrElse computes a fallback from the exception, and getOrThrow re-throws the failure's exception.

solid answer

~40 s

These are the terminal getters on Result<T>. getOrNull(): T? returns the value on success or null on failure. getOrDefault(defaultValue): T returns the value or an eagerly-evaluated default; the default is computed even on success because it's a plain argument. getOrElse { e -> ... }: T takes a lambda receiving the Throwable, so the fallback is lazy and can depend on the exception. getOrThrow(): T returns the value or re-throws the captured exception. Prefer getOrElse over getOrDefault when the fallback is expensive (lazy) or when you need the exception. getOrNull is for when failure simply means 'no value'. getOrThrow turns a Result back into normal exception flow, useful at a boundary where you've finished functional handling and want exceptions again.

code

kotlin · 8 lines
kotlin
val r: Result<Int> = runCatching { "7".toInt() }
val failing: Result<Int> = runCatching { "x".toInt() }

println(r.getOrNull())              // 7
println(failing.getOrNull())        // null
println(failing.getOrDefault(-1))   // -1 (eager)
println(failing.getOrElse { e -> e.message?.length ?: 0 }) // lazy, sees e
// failing.getOrThrow()              // throws NumberFormatException

go deeper

for a junior

Can state each getter's return on success and failure and pick getOrNull vs getOrThrow correctly.

for a middle

Articulates the eager-vs-lazy distinction between getOrDefault and getOrElse and that getOrElse receives the Throwable.

for a senior

Discusses inline semantics, return-type widening to R, and when re-throwing via getOrThrow at a boundary is the right design.

for a principal

Frames getter choice as part of an error-handling boundary strategy and reasons about API ergonomics of exposing Result vs unwrapping it.

## What is Result<T>? `Result<T>` is a value class in the Kotlin standard library representing either a successful value of type `T` or a failure carrying a `Throwable`. You inspect it with `isSuccess`/`isFailure` or, more commonly, with the consuming functions below. It is *not* a monad transformer or `Either` — it only ever holds a `Throwable` on the failure side. ## The four value getters - **`getOrNull(): T?`** — returns the value on success, or `null` on failure. The exception is discarded. Best when failure just means "absent". - **`getOrDefault(defaultValue: R): R`** — returns the value on success, otherwise the `defaultValue` argument. Because `defaultValue` is an ordinary parameter, it is **evaluated eagerly** (always computed, even when the Result is a success and the default is unused). - **`getOrElse(onFailure: (Throwable) -> R): R`** — returns the value on success, otherwise the result of calling `onFailure` with the captured `Throwable`. The lambda is **lazy** (only invoked on failure) and **has access to the exception**. It is `inline`, so the lambda body can do non-local returns from the enclosing function. - **`getOrThrow(): T`** — returns the value on success, otherwise **re-throws** the stored `Throwable`. This converts the Result back into ordinary exception-based control flow. ## Why getOrElse usually beats getOrDefault ```kotlin val r: Result<Int> = runCatching { compute() } // Eager: expensiveFallback() runs even on success val a = r.getOrDefault(expensiveFallback()) // Lazy: lambda runs only on failure, and sees the exception val b = r.getOrElse { e -> log.warn("compute failed: ${e.message}") -1 } ``` `getOrDefault` cannot see the exception and always evaluates its argument; `getOrElse` is lazy and exception-aware. Reach for `getOrDefault` only when the default is a cheap constant. ## Reading the exception `exceptionOrNull(): Throwable?` returns the captured throwable on failure or `null` on success — the mirror image of `getOrNull()`. Use it when you want the error rather than the value. ## Key keywords These are `inline` extension functions; `getOrElse` and `getOrDefault` widen the return type to `R` (a supertype of `T`), so the fallback may be of a broader type.

  • If a Result is a success, does getOrDefault still evaluate its argument expression?
    Yes. The default is a normal eagerly-evaluated argument, so it is computed regardless and simply ignored on success. getOrElse avoids that by taking a lambda.
  • How would you get the Throwable instead of the value?
    Use exceptionOrNull(): Throwable? — it returns the captured exception on failure or null on success.

getOrNull says 'nothing here', getOrDefault hands you a spare you prepared in advance, getOrElse builds a spare on demand after seeing what broke, getOrThrow just lets the original failure fly again.

saying these in an interview costs you the question

  • Claiming getOrDefault evaluates its argument lazily
  • Saying getOrElse cannot see the exception
  • Thinking getOrThrow returns null on failure instead of throwing
  • Confusing Result with Either / saying it can hold an arbitrary error type
  • Believing getOrNull rethrows or logs the exception

context