Why does `produce` return a `ReceiveChannel` while `actor` returns a `SendChannel`, and what are the receiver scopes inside each?
answer
- produce out -> caller gets ReceiveChannel
- actor in -> caller gets SendChannel
- ProducerScope = scope + SendChannel
- ActorScope = scope + ReceiveChannel
- caller always gets the opposite end
basics
~20 sWith 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 sA `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// 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
Recognizes you receive from produce and send to actor.
Explains the SendChannel/ReceiveChannel split and the inner receiver scopes for each builder.
Reasons about direction-of-data-flow as the design principle and notes both scopes are CoroutineScopes.
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)