skip to content

produce & actor Builders

produce gives you a coroutine that streams results through a ReceiveChannel, and actor hides mutable state behind a SendChannel so only one coroutine ever touches it. The actor pattern is a standard answer to shared-state questions.

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

questions

5

Why does `produce` return a `ReceiveChannel` while `actor` returns a `SendChannel`, and what are the receiver scopes inside each?

level: middleimportance: must knowfreq 35%

answer

  1. produce out -> caller gets ReceiveChannel
  2. actor in -> caller gets SendChannel
  3. ProducerScope = scope + SendChannel
  4. ActorScope = scope + ReceiveChannel
  5. caller always gets the opposite end

basics

~20 s

With produce the coroutine makes values for you, so you get the read end (ReceiveChannel). With actor you feed it messages, so you get the write end (SendChannel). Each is the opposite end of the channel from what the coroutine uses.

solid answer

~30 s

A `Channel<T>` has two interfaces: `SendChannel<T>` (write) and `ReceiveChannel<T>` (read). The builders expose the **caller's** end and keep the coroutine's end inside. `produce` is a *producer*: the coroutine `send`s, so the caller `receive`s — it returns `ReceiveChannel<T>`, and inside the receiver is `ProducerScope<T>` (`CoroutineScope` + `SendChannel<T>`). `actor` is a *consumer of messages*: the caller `send`s, so the coroutine `receive`s — it returns `SendChannel<T>`, and inside the receiver is `ActorScope<T>` (`CoroutineScope` + `ReceiveChannel<T>`). The asymmetry mirrors data-flow direction: produce streams out, actor takes in. This is why you iterate `for (x in produceResult)` but `send` to an actor.

code

kotlin · 7 lines
kotlin
// produce: coroutine sends, caller receives
val rc: ReceiveChannel<Int> = produce { send(42) }
println(rc.receive())

// actor: caller sends, coroutine receives
val sc: SendChannel<String> = actor { for (m in channel) println(m) }
sc.send("hi"); sc.close()

go deeper

for a junior

Recognizes you receive from produce and send to actor.

for a middle

Explains the SendChannel/ReceiveChannel split and the inner receiver scopes for each builder.

for a senior

Reasons about direction-of-data-flow as the design principle and notes both scopes are CoroutineScopes.

for a principal

Uses the symmetry to design channel-based pipelines and decide builder choice for an API boundary.

## Two faces of a channel A `Channel<T>` implements two narrower interfaces: - **`SendChannel<T>`** — `send(x)`, `trySend(x)`, `close()`. The **write** end. - **`ReceiveChannel<T>`** — `receive()`, `tryReceive()`, iteration via `for`/`consumeEach`. The **read** end. A coroutine builder that wires a channel to a coroutine keeps **one end for the coroutine** (inside the lambda) and **returns the other end to the caller**. Which end the caller gets depends on the **direction of data flow**. ## produce: data flows OUT The `produce` coroutine **generates** values. Inside, it must be able to **send**, so its receiver is **`ProducerScope<T>` = `CoroutineScope` + `SendChannel<T>`**. The caller wants to **read** those values, so `produce` returns a **`ReceiveChannel<T>`**. ```kotlin val out: ReceiveChannel<Int> = produce { send(1); send(2) } // inside: SendChannel for (x in out) println(x) // outside: receive ``` ## actor: data flows IN The `actor` coroutine **consumes** messages. Inside, it must **receive**, so its receiver is **`ActorScope<T>` = `CoroutineScope` + `ReceiveChannel<T>`**. The caller wants to **write** messages, so `actor` returns a **`SendChannel<T>`**. ```kotlin val inbox: SendChannel<Msg> = actor { for (m in channel) handle(m) } // inside: ReceiveChannel inbox.send(SomeMsg) // outside: send ``` ## The symmetry table | Builder | Returns to caller | Receiver scope inside | Inside has | |---------|------------------|-----------------------|-----------| | `produce` | `ReceiveChannel<T>` | `ProducerScope<T>` | `SendChannel<T>` | | `actor` | `SendChannel<T>` | `ActorScope<T>` | `ReceiveChannel<T>` | Both scopes are also a `CoroutineScope`, so you can `launch` child coroutines inside either. The mental model: **the builder hands the caller the end opposite to the one the coroutine uses**, and the direction is dictated by whether the coroutine produces (out) or consumes (in).

  • Can you both send to and receive from a produce result?
    No. produce returns only a ReceiveChannel, so you can only receive. The send side is private to the coroutine.
  • Both ProducerScope and ActorScope extend what common interface?
    Both extend CoroutineScope, so you can launch nested coroutines inside either builder.

saying these in an interview costs you the question

  • Swapping the two: saying produce returns SendChannel or actor returns ReceiveChannel
  • Thinking the returned channel exposes both send and receive
  • Not knowing the inner receiver scopes (ProducerScope / ActorScope)
  • Assuming you can close() a ReceiveChannel returned by produce (you cancel it instead)

context

open as a page

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

level: juniorimportance: should knowfreq 45%

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.

open as a page

How does the `actor` builder let you safely mutate shared state without a Mutex?

level: middleimportance: should knowfreq 40%

basics

~20 s

actor runs a single coroutine that owns the state and processes messages one at a time from its inbox. Because only that one coroutine touches the state, there is no race, so you don't need a lock.

open as a page

When consuming a `produce` channel, why is `consume`/`consumeEach` recommended, and what cleanup do they guarantee?

level: seniorimportance: should knowfreq 28%

basics

~20 s

If you stop reading early without cleanup, the producer coroutine can hang waiting forever. consume and consumeEach cancel the channel when you're done or if you break out, which stops the producer and frees resources.

open as a page

Both `produce` and `actor` are flagged experimental/obsolete. As a principal engineer, when would you still use them, and what modern alternatives would you reach for instead?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

produce is good for hot, channel-based streaming pipelines; for most output streams prefer Flow. actor serializes state via messages, but today a Mutex or a StateFlow is usually simpler and stable. Use the builders when the channel semantics genuinely fit.

open as a page