skip to content

What does the `produce` coroutine builder do, and what does it return?

level: juniorimportance: should knowfreq 45%

answer

  1. produce -> ReceiveChannel
  2. receiver is ProducerScope = scope + SendChannel
  3. send() inside, for/consumeEach outside
  4. auto-closes on return or throw
  5. ExperimentalCoroutinesApi

basics

~10 s

produce starts a coroutine that streams out values. It gives you back a channel you can read from to receive each value the coroutine sends.

solid answer

~30 s

`produce { }` is a coroutine builder, an extension on `CoroutineScope`, that launches a coroutine and returns a `ReceiveChannel<T>`. Inside the block you call `send(value)` (the receiver is a `ProducerScope<T>`) to push items to consumers. It is the producer side of a channel wired to a coroutine in one step. When the block finishes or throws, the channel is automatically closed, so consumers iterating with `for (x in channel)` or `consumeEach { }` stop cleanly. It is `ExperimentalCoroutinesApi`. It is ideal for emitting a finite or infinite stream of computed results without manually creating a `Channel` and a separate `launch`.

code

kotlin · 7 lines
kotlin
fun CoroutineScope.squares(n: Int): ReceiveChannel<Int> = produce {
    for (i in 1..n) send(i * i)
}

suspend fun main() = coroutineScope {
    for (sq in squares(4)) println(sq)   // 1 4 9 16
}

go deeper

for a junior

Knows produce starts a coroutine and returns a channel you read from with a for loop.

for a middle

Explains ProducerScope, auto-close semantics, and consumeEach/consume.

for a senior

Contrasts produce with Flow (hot vs cold), notes ExperimentalCoroutinesApi, discusses backpressure via channel capacity.

for a principal

Weighs produce vs Flow for an API surface, structured-concurrency implications, and cancellation/cleanup guarantees across module boundaries.

## What `produce` is `produce` is a **coroutine builder** in `kotlinx.coroutines`. A coroutine builder is a function that starts a coroutine; familiar ones are `launch` (fire-and-forget) and `async` (returns a `Deferred`). `produce` is the channel-backed one: it starts a coroutine **and** hands you a `ReceiveChannel<T>` connected to it. ```kotlin fun CoroutineScope.numbers(): ReceiveChannel<Int> = produce { for (i in 1..5) send(i) // send is on ProducerScope } ``` ## Key pieces - **`ReceiveChannel<T>`** — the read-only side of a channel; consumers call `receive()` or iterate `for (x in channel)`. - **`ProducerScope<T>`** — the receiver type inside the lambda. It is a `CoroutineScope` **plus** a `SendChannel<T>`, so you can call `send(...)` directly. - **Auto-close** — when the block returns normally or throws, the channel is **closed automatically**. A normal return closes it normally (consumers see the loop end); an exception closes it with that cause, which is re-thrown to the consumer. ## Consuming ```kotlin val ch = numbers() for (x in ch) println(x) // stops when producer closes // or: ch.consumeEach { println(it) } ``` `consumeEach` and the `for` loop both stop when the channel closes. `consume { }` cancels the channel on exit, which is the safe way to drain a `produce` result even if you break early. ## Why use it Without `produce` you would create a `Channel`, `launch` a coroutine, remember to `close()` it, and handle exceptions yourself. `produce` does all of that in one expression. Note it is marked `@ExperimentalCoroutinesApi`.

  • What happens to the channel if the producer block throws?
    The channel is closed with that exception as its cause, and the exception is re-thrown to whoever is receiving.
  • Do you need to call close() yourself inside produce?
    No. The builder closes the channel automatically when the block completes or fails.

Like a vending machine conveyor: the coroutine drops items on the belt (send), you take them off the end (receive), and when it runs out the belt stops (close).

saying these in an interview costs you the question

  • Saying produce returns a SendChannel (it returns ReceiveChannel; you send inside)
  • Thinking you must manually close() the channel
  • Confusing produce with flow (produce is hot and channel-backed)
  • Claiming send() is available on the outer scope rather than ProducerScope

context