skip to content

Channel Basics

A Channel is a suspending queue between coroutines, with the capacity option — rendezvous, buffered, conflated, unlimited — deciding what happens when producer and consumer run at different speeds. Closing it correctly, and consuming with consumeEach, is the practical half.

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

questions

5

What is a Channel<T> in Kotlin coroutines, and how do producer and consumer coroutines communicate through it?

level: juniorimportance: must knowfreq 70%

answer

  1. send/receive are suspend
  2. hot, not cold like Flow
  3. RENDEZVOUS = no buffer, direct handover
  4. for (x in channel) iterates receiver
  5. close() ends the loop

basics

~10 s

A Channel is a pipe between coroutines. One coroutine puts values in with send, another takes them out with receive. Both suspend (wait politely) until the other side is ready.

solid answer

~40 s

Channel<T> is a coroutine-safe queue used to pass a stream of values between coroutines. A producer calls suspend fun send(value), a consumer calls suspend fun receive(). Both are suspending functions: with the default RENDEZVOUS capacity, send suspends until a receiver is ready and receive suspends until a value is available, so the channel hands values over directly with no buffering. Unlike Flow (cold), a Channel is hot: values flow as soon as both ends meet, regardless of whether anyone is collecting yet. You create one with Channel<Int>() and typically iterate the consumer side with for (x in channel) or channel.consumeEach { }. When the producer is done it calls close(), which lets the consumer loop end cleanly.

code

kotlin · 6 lines
kotlin
val channel = Channel<Int>()
launch {
    for (x in 1..3) channel.send(x)
    channel.close()
}
for (y in channel) println(y)  // 1 2 3

go deeper

for a junior

Knows send/receive exist, that they suspend, and that close ends the loop.

for a middle

Explains hot vs cold vs Flow and the rendezvous handover precisely.

for a senior

Discusses when a Channel is the right tool vs a Flow and the resource/ownership implications.

for a principal

Frames Channel as the low-level communication primitive underlying produce/actor and shared-flow design tradeoffs.

## What a Channel is A `Channel<T>` is a concurrency primitive in `kotlinx.coroutines` for passing a *stream* of values of type `T` between coroutines. Think of it as a coroutine-aware `BlockingQueue`: instead of blocking a thread, its operations **suspend** the calling coroutine, freeing the thread to do other work. ## The two core operations - `suspend fun send(element: T)` — a **producer** offers a value into the channel. - `suspend fun receive(): T` — a **consumer** takes the next value out. Both are `suspend` functions, so they can only be called from a coroutine or another suspend function. "Suspend" means the coroutine pauses without blocking the underlying thread and resumes later when the operation can proceed. ## Hot vs cold A Channel is a **hot** stream: it exists and conducts values independently of consumers. This contrasts with `Flow`, which is **cold** (the producing code reruns for each collector). A Channel models *communication between coroutines*; a Flow models a *cold declarative pipeline*. ## Rendezvous by default With no capacity argument, `Channel<T>()` uses `RENDEZVOUS`: there is no buffer. `send` suspends until some coroutine calls `receive`, and vice versa. The value is handed over directly — a meeting point ("rendezvous"). ## Iterating the consumer side The channel implements `ReceiveChannel<T>`, which is iterable in a coroutine: ```kotlin import kotlinx.coroutines.* import kotlinx.coroutines.channels.* fun main() = runBlocking { val channel = Channel<Int>() launch { // producer coroutine for (x in 1..3) channel.send(x * x) channel.close() // signal: no more values } for (y in channel) { // suspends until each value / close println(y) // 1, 4, 9 } } ``` The `for (y in channel)` loop calls `receive` under the hood and ends when the channel is **closed and drained**. ## Closing `close()` marks the channel as done. Pending buffered values are still delivered; afterwards the consumer loop terminates. Calling `send` on a closed channel throws `ClosedSendChannelException`. ## Why use one Channels are the building block for producer/consumer pipelines, fan-out/fan-in work distribution, and the higher-level `produce { }` and `actor { }` builders.

  • How is a Channel different from a Flow?
    Channel is hot and is a communication primitive between coroutines; Flow is cold and reruns its producer per collector. Channels have explicit send/receive; Flow has emit/collect.
  • What happens to receive when the channel is closed and empty?
    receive() throws ClosedReceiveChannelException; the for-loop and consumeEach handle this for you and just exit.

Like passing a baton in a relay: the runner (sender) waits with the baton until the next runner (receiver) is there to take it.

saying these in an interview costs you the question

  • Saying a Channel is cold or reruns per consumer
  • Claiming send/receive block the thread instead of suspending
  • Thinking you can call send/receive from a normal (non-suspend) function
  • Forgetting to close() and wondering why the consumer loop hangs forever

context

open as a page

Explain the Channel capacity options RENDEZVOUS, BUFFERED, CONFLATED, and UNLIMITED, and how each affects send.

level: middleimportance: must knowfreq 60%

basics

~10 s

Capacity controls how many values a channel holds before send has to wait. Rendezvous holds none, buffered holds a few, unlimited holds everything, and conflated keeps only the latest and never waits.

open as a page

How do you correctly close a Channel and iterate it, and what is the difference between for-in, consumeEach, and consume?

level: middleimportance: should knowfreq 50%

basics

~10 s

The producer calls close() when done. The consumer reads with a for-in loop or consumeEach. consumeEach also cancels the channel when finished, so it cleans up even if something goes wrong.

open as a page

How do Channels enable fan-out and fan-in, and what ordering/fairness guarantees apply when multiple coroutines share one channel?

level: seniorimportance: should knowfreq 38%

basics

~10 s

Many workers can read from one channel (fan-out) to split work, and many producers can write to one channel (fan-in) to merge results. The channel hands each value to exactly one receiver.

open as a page

What are trySend, tryReceive, and receiveCatching on a Channel, and when would you use them instead of send/receive?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

trySend and tryReceive are non-suspending attempts: they try once and tell you whether it worked, without waiting. receiveCatching waits but returns a result object instead of throwing when the channel is closed.

open as a page