skip to content

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