skip to content

Inside a `catch { }` block, what are your options for the caught throwable, and how do you selectively handle only certain exception types?

level: seniorimportance: should knowfreq 40%

answer

  1. Swallow / recover (emit) / propagate (throw)
  2. Receiver is FlowCollector → emit available
  3. Branch on type, rethrow the rest
  4. NEVER swallow CancellationException
  5. Translate to domain exceptions for clean APIs

basics

~10 s

Inside catch you can ignore the error, log it, send a backup value with emit, or rethrow it. To handle only some error types, check the type and rethrow the rest.

solid answer

~40 s

Inside `catch { e -> }` the throwable is bound (commonly as `it`). Options: (1) swallow — do nothing, flow completes normally; (2) recover — call `emit`/`emitAll` to provide fallback values; (3) propagate — `throw it` or throw a translated exception. Because `catch` catches *all* upstream `Throwable`s, selective handling is done manually: branch on the type and rethrow what you don't own, e.g. `if (it is IOException) emit(fallback) else throw it`. Be careful never to swallow `CancellationException` — `catch` will receive it if the upstream was cancelled, and silently handling it breaks structured concurrency. Idiomatically, rethrow cancellation: many codebases write `if (it is CancellationException) throw it`. Use translated domain exceptions to keep the contract clean for callers.

code

kotlin · 9 lines
kotlin
userFlow
    .catch { e ->
        if (e is CancellationException) throw e
        when (e) {
            is IOException -> emit(User.GUEST)   // recover
            else -> throw DomainError("load failed", e) // translate + propagate
        }
    }
    .collect(::show)

go deeper

for a junior

Knows you can log or emit a fallback inside catch.

for a middle

Knows the swallow/recover/propagate options and basic type branching.

for a senior

Explicitly guards CancellationException and translates exceptions for a clean contract.

for a principal

Defines a team convention (rethrow cancellation, domain translation, recover only known-safe types) and reasons about partial-failure semantics.

## The lambda's receiver and parameter `catch(action: suspend FlowCollector<T>.(Throwable) -> Unit)`. So inside the block: - the receiver is a `FlowCollector<T>` → `emit` / `emitAll` are available; - the parameter is the caught `Throwable` (default name `it`). ## Three things you can do 1. **Swallow** — log/ignore; the flow then completes successfully (no more emissions). 2. **Recover** — `emit(default)` or `emitAll(fallbackFlow)`. 3. **Propagate** — `throw it` (re-raise) or `throw DomainException(it)` (translate). ## Selective handling by type `catch` is type-agnostic — it intercepts every upstream `Throwable`. To handle only some, branch and rethrow the rest: ```kotlin repository.stream() .catch { e -> when (e) { is IOException -> emit(cachedFallback) // recover network errors else -> throw e // let everything else propagate } } .collect(::render) ``` ## Watch out: CancellationException If the collecting coroutine is cancelled, a `CancellationException` may surface as an upstream throwable in `catch`. **Swallowing it breaks structured concurrency** — the coroutine would appear to finish normally instead of being cancelled. Always rethrow it: ```kotlin .catch { e -> if (e is CancellationException) throw e emit(fallback) } ``` (Modern coroutines try to make cancellation propagate, but explicit rethrow is the safe, idiomatic guard, mirroring the general rule for catching `Throwable` in coroutine code.) ## Translating exceptions For clean APIs, convert low-level failures into domain types so callers don't depend on transport details: ```kotlin .catch { throw DataUnavailableException(cause = it) } ``` ## Key APIs / keywords - `catch { }`, `emit`, `emitAll`, `throw`. - `CancellationException` — must be rethrown. - `is` type checks / `when` for selective handling.

  • Why must you rethrow `CancellationException` from inside `catch`?
    Because it signals structured-concurrency cancellation; swallowing it makes a cancelled coroutine look like it completed normally, leaking work and breaking parent/child cancellation.
  • How do you handle only `IOException` and propagate everything else?
    Type-check inside the block: `if (it is IOException) emit(fallback) else throw it`.

saying these in an interview costs you the question

  • Swallowing all throwables including CancellationException
  • Assuming `catch` only catches the type you care about
  • Not knowing you can emit/emitAll inside catch
  • Leaking transport exceptions to callers instead of translating

context