skip to content

retry / retryWhen

retry resubscribes to a failed upstream a fixed number of times, and retryWhen gives you the cause and attempt count so you can implement conditional retries and backoff. Interviewers want to hear that you do not retry non-transient failures.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What does the Flow operator retry(count) do, and where does it sit in a Flow pipeline?

level: juniorimportance: must knowfreq 62%

answer

  1. Re-subscribes to upstream on failure
  2. retries = EXTRA attempts (retry(3) = 4 total)
  3. Only upstream errors, not downstream
  4. Never retries CancellationException
  5. Producer block re-runs, side effects repeat

basics

~10 s

retry re-subscribes to the flow when it fails with an error, trying again up to the given number of times before letting the error through.

solid answer

~40 s

retry(retries: Long) is an intermediate Flow operator that catches an exception thrown by the upstream and re-collects (re-subscribes to) the upstream from scratch, up to `retries` extra attempts. If all attempts fail, the last exception propagates downstream. It only affects exceptions from upstream operators placed BEFORE retry in the chain; anything after retry (downstream) is not retried. By default it retries on any Throwable; the overload retry(retries, predicate) lets you decide per-exception. Because it re-runs the upstream, the producer block (the `flow { }` lambda) executes again, so side effects there repeat. retry sits like any intermediate operator: upstream -> retry -> downstream collectors.

code

kotlin · 8 lines
kotlin
val data: Flow<String> = flow {
    println("attempting fetch")
    emit(client.get(url)) // throws on network error
}

data
    .retry(2) // first try + 2 retries = 3 attempts max
    .collect { println("got: $it") }

go deeper

for a junior

Knows retry re-runs the flow on error and that the count is extra attempts.

for a middle

Explains upstream-only scope and that the producer block re-executes with repeated side effects.

for a senior

Articulates the exception-transparency boundary and CancellationException exclusion.

for a principal

Frames retry in terms of idempotency requirements and where retry belongs in a resilience strategy vs downstream concerns.

## What `retry` is `retry` is an **intermediate operator** on `kotlinx.coroutines.flow.Flow`. An intermediate operator returns a new `Flow` and runs lazily — nothing happens until a **terminal operator** (like `collect`) starts collection. When collection fails because the **upstream** (everything emitted before `retry` in the chain) throws, `retry` re-subscribes: it cancels the failed collection and starts collecting the upstream again from the beginning. ```kotlin flow { emit(fetchPage()) // may throw IOException } .retry(3) // up to 3 EXTRA attempts (4 total) .collect { println(it) } ``` ## Signature - `fun <T> Flow<T>.retry(retries: Long = Long.MAX_VALUE, predicate: suspend (cause: Throwable) -> Boolean = { true }): Flow<T>` - `retries` is the number of **additional** attempts after the first, so `retry(3)` means at most 4 total runs. - `retries` must be `>= 0`; passing a negative value throws `IllegalArgumentException`. ## Upstream vs downstream `retry` only re-runs operators **above** it. Exceptions from operators **below** `retry` (downstream, e.g. inside `collect`) are NOT retried — they escape. This is the same "exception transparency" boundary that `catch` honors: an operator only handles upstream failures. ## Side effects repeat Because the upstream `flow { }` block runs again on each attempt, any side effect inside it (a network call, a counter increment) executes again. That is usually the point (re-fetch), but be aware emitted values from before the failure are re-emitted too. ## Cancellation is respected `retry` does NOT retry on `CancellationException` — coroutine cancellation must always propagate. The default predicate returns `true` for other throwables but cancellation short-circuits the retry machinery so a cancelled flow stops immediately. ## Recap of operator placement - Put `retry` **after** the failing upstream and **before** your `collect`. - Use the no-arg-ish `retry(n)` for a simple fixed cap; use `retryWhen { cause, attempt -> }` (covered separately) for conditional logic and backoff.

  • Does retry(3) mean 3 total executions?
    No. It means 3 EXTRA attempts after the initial one, so up to 4 total collections of the upstream.
  • Will retry catch an exception thrown inside collect { }?
    No. collect is downstream of retry; retry only re-subscribes on upstream failures.

Like redialing a dropped phone call: you start the whole call over, up to a few times, before giving up.

saying these in an interview costs you the question

  • Thinking retry(n) runs exactly n times total
  • Believing retry catches downstream/collect errors
  • Claiming retry swallows CancellationException
  • Assuming the producer block does not re-execute
  • Confusing retry with catch (which doesn't re-subscribe)

context

open as a page

How does retryWhen { cause, attempt -> } work, and what do its parameters mean?

level: middleimportance: must knowfreq 55%

basics

~10 s

retryWhen runs a function each time the flow fails. You get the error and how many times it has already retried, and you return true to try again or false to give up.

open as a page

Why does retry require the upstream Flow to be cold/restartable, and what happens if you retry a hot or non-idempotent source?

level: middleimportance: should knowfreq 28%

basics

~20 s

Retry works by starting the flow over from the beginning. That only makes sense for a cold flow that re-runs cleanly. With a hot or one-shot source, restarting may do nothing useful or repeat real side effects.

open as a page

How do retry/retryWhen interact with CancellationException and exception transparency? What surprises engineers?

level: seniorimportance: should knowfreq 33%

basics

~10 s

Retry never restarts a flow that was cancelled — cancellation always wins. And retry only restarts the flow for errors that came from above it, not from your collector below.

open as a page

Implement exponential backoff with jitter for a flaky network Flow using retryWhen, retrying only transient errors. Walk through the design.

level: seniorimportance: should knowfreq 45%

basics

~10 s

Use retryWhen: check the error is retryable and you haven't hit the cap, wait a growing amount of time (doubling each try) plus a small random delay, then retry; otherwise give up.

open as a page