Channels Quiz
KOTLIN › Concurrency
What does send() do when the channel is full?
- It suspends the calling coroutine until space is available in the channel buffer
- It drops the oldest element to make room, like a ring buffer would do
- It throws BufferOverflowException immediately without suspending the coroutine at all
- It blocks the current thread until a receiver drains at least one element
Answer: It suspends the calling coroutine until space is available in the channel buffer
send() is a suspend function. When the buffer is full, it suspends (not blocks) the coroutine until another coroutine calls receive() and frees a slot.
What is the capacity of a Channel<Int>() created with no arguments?
- 0 (rendezvous): send always suspends until a matching receive is ready
- 64 (the default BUFFERED size that kotlinx.coroutines ships with)
- 1 (a single-element buffer that suspends only on the second unread send)
- Unlimited: the buffer grows dynamically and send never suspends at all
Answer: 0 (rendezvous): send always suspends until a matching receive is ready
The default is Channel.RENDEZVOUS (capacity 0). Every send() suspends until a receive() is waiting, and vice versa. You must explicitly pass a capacity or use Channel.BUFFERED / Channel.CONFLATED / Channel.UNLIMITED for other modes.
What does Channel.CONFLATED do when a new element is sent before the previous one is received?
- It overwrites the buffered element with the new one, so the receiver only sees the latest value
- It queues both elements in insertion order behind each other so no data is ever lost in the pipeline
- It suspends the sender until the receiver has consumed the older element first
- It throws ClosedSendChannelException because the single slot is already occupied
Answer: It overwrites the buffered element with the new one, so the receiver only sees the latest value
A conflated channel keeps only the most recent element. Older unread values are silently dropped. This is useful when only the latest state matters, like UI updates.
What does produce {} return and what happens when its lambda completes?
- A ReceiveChannel that is automatically closed when the producer block finishes
- A SendChannel that the caller must close manually after all items have been sent
- A Flow that replays the produced elements to every new collector that subscribes
- A Deferred that resolves to the list of all produced elements once the block returns
Answer: A ReceiveChannel that is automatically closed when the producer block finishes
produce {} is a coroutine builder that returns ReceiveChannel<T>. When the block completes, the channel is closed automatically. This is safer than manually creating and closing a channel.
Two coroutines both call receive() on the same channel. What happens when a value is sent?
- Exactly one receiver gets the value; the other stays suspended until the next send
- Both receivers get a copy of the value, since channels broadcast to all consumers
- The channel throws IllegalStateException because multiple receivers are not allowed
- The value is dropped because the channel cannot determine which receiver should win
Answer: Exactly one receiver gets the value; the other stays suspended until the next send
Channels deliver each element to exactly one receiver (fan-out). There is no broadcast. If you need every consumer to see every element, use SharedFlow or BroadcastChannel (deprecated).
What is the difference between send() and trySend()?
- send() suspends when the buffer is full; trySend() returns immediately with a result
- send() is thread-safe while trySend() requires external synchronization to be safe
- trySend() throws on failure while send() silently drops the element on a full channel
- send() works only inside produce {} while trySend() works from any coroutine scope
Answer: send() suspends when the buffer is full; trySend() returns immediately with a result
send() is a suspend function: it waits for capacity. trySend() is non-suspending and returns a ChannelResult (success/failure/closed). Use trySend() from callbacks or other non-suspend contexts.
Why are channels described as 'hot' compared to flows which are 'cold'?
- A channel exists and can buffer data whether or not anyone is receiving from it right now
- Channels run on a dedicated background thread while flows always share the caller's thread pool
- Hot means the channel uses more memory because it pre-allocates its entire buffer at creation
- Channels automatically retry failed sends while flows propagate errors to the collector only
Answer: A channel exists and can buffer data whether or not anyone is receiving from it right now
A channel is hot: once created, producers can send into it regardless of consumers. A flow is cold: nothing executes until collect() is called. This is the fundamental difference in their execution model.
When should you use channelFlow {} instead of flow {}?
- When you need to emit values from multiple concurrent coroutines inside the flow builder
- When you want the flow to buffer its output so the collector never suspends on slow emissions
- When the flow must survive configuration changes and outlive the collecting lifecycle owner
- When you need to emit values synchronously without suspending, which flow {} cannot do at all
Answer: When you need to emit values from multiple concurrent coroutines inside the flow builder
flow {} runs in a single coroutine and emit() must be called from that coroutine. channelFlow {} gives you a ProducerScope where you can launch child coroutines that all call send(). It is cold (like flow) but internally backed by a channel for concurrent emission.
What happens when you call receive() on a closed, empty channel?
- It throws ClosedReceiveChannelException because the channel is both closed and drained
- It returns null to signal that no more elements will ever arrive from the closed producer side
- It suspends indefinitely waiting for a new element, since close is merely advisory
- It returns the last element that was sent before the channel was closed, as a fallback
Answer: It throws ClosedReceiveChannelException because the channel is both closed and drained
After close(), buffered elements can still be received. Once drained, receive() throws ClosedReceiveChannelException. Use receiveCatching() for a non-throwing alternative that returns ChannelResult.
What does select {} do in the context of channels?
- It suspends until the first of several channel operations is ready, then executes that branch
- It rounds-robin across channels, reading one element from each in a deterministic fixed order
- It merges all listed channels into a single combined channel that the caller can receive from
- It filters elements from a channel, selecting only those matching a predicate function guard clause
Answer: It suspends until the first of several channel operations is ready, then executes that branch
select {} is a suspending multiplexer. You list channel operations (onReceive, onSend) as clauses. It suspends until one is ready, executes that clause's lambda, and returns its result. It is like a suspending switch statement over channel readiness.
What was the actor {} builder used for, and why is it deprecated?
- It created a coroutine with its own mailbox channel, replaced by safer patterns like shared flows
- It spawned a dedicated thread for CPU-intensive work, replaced by Dispatchers.Default instead
- It broadcasted events to multiple consumers, replaced by SharedFlow which handles replay better
- It ran blocking I/O in a coroutine, replaced by withContext(Dispatchers.IO) for thread safety reasons
Answer: It created a coroutine with its own mailbox channel, replaced by safer patterns like shared flows
actor {} combined a coroutine with a SendChannel mailbox, serializing messages for safe state mutation. It was deprecated because the pattern is fragile (easy to leak the channel) and SharedFlow / MutableStateFlow cover most use cases more safely.
How does consumeEach {} differ from a for loop over a channel?
- It cancels the channel's producer on any exception, while a for loop just stops iterating and leaks it
- It reads elements in batches of N for throughput, while a for loop reads them one by one sequentially
- It resumes from the last consumed element after a crash, while a for loop starts over from scratch
- It converts the channel into a flow and collects it, while a for loop uses the channel iterator protocol
Answer: It cancels the channel's producer on any exception, while a for loop just stops iterating and leaks it
consumeEach cancels the underlying ReceiveChannel if the block throws, ensuring the producer coroutine is cancelled and resources are freed. A bare for loop does not cancel the channel, so the producer keeps running on failure.
What is the fan-in pattern with channels?
- Multiple producer coroutines all send into one shared channel that a single consumer reads from
- One producer sends the same element to multiple channels simultaneously via a broadcast mechanism
- A single channel automatically load-balances its output across a pool of consumer coroutines
- Multiple channels are merged into a single flow using the combine operator for synchronized reads
Answer: Multiple producer coroutines all send into one shared channel that a single consumer reads from
Fan-in means many-to-one: several producers send into the same channel, and one (or more) consumers receive from it. The channel serializes all the sends into a single ordered stream.
What is the main risk of using Channel.UNLIMITED?
- The buffer grows without bound, so a fast producer can cause an OutOfMemoryError crash
- Elements are silently dropped when the JVM runs low on memory, with no failure notification
- It forces every send to allocate a new coroutine, which exhausts the dispatcher's thread pool
- Receivers must consume elements within a timeout or the channel is automatically closed by the runtime
Answer: The buffer grows without bound, so a fast producer can cause an OutOfMemoryError crash
UNLIMITED means the internal buffer has no cap. send() never suspends, so a producer that outpaces its consumer fills memory indefinitely. Prefer BUFFERED or RENDEZVOUS for back-pressure.