Channels Quiz

KOTLIN › Concurrency

What does send() do when the channel is full?

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?

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?

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?

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?

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()?

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'?

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 {}?

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?

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?

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?

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?

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?

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?

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.

Back to Channels