Channels (buffered, unbuffered, close)
Channels are how goroutines talk to each other safely. By the end of this lesson you'll send and receive values, tell unbuffered from buffered channels, close a channel and range over it, and use select to wait on several channels at once — Go's "communicate to share memory" philosophy in action.
Learn Channels (buffered, unbuffered, close) in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
Only the sender should close a channel, and never send on a closed channel — both rules keep you out of panics.
1️⃣ Send and Receive (Unbuffered)
Make a channel with , send with , receive with . An unbuffered channel is a rendezvous: the send blocks until a receiver is ready, and the receive blocks until a value arrives. That handshake also synchronizes the two goroutines for free.
Channels shine for returning a value out of a goroutine — there's no return across the go boundary, so you send the result back instead.
Your turn. Fill in the send and the receive so the value travels through the channel.
2️⃣ Buffered Channels
Give make a second argument and you get a buffered channel that holds that many values without a receiver waiting. Sends only block once the buffer is full; receives only block once it's empty. len(ch) tells you how many values are queued, cap(ch) the capacity.
3️⃣ Closing and Ranging
close(ch) announces that no more values are coming. A receiver can then range over the channel and the loop ends automatically once every queued value has been read. The v, ok := form sets ok to false when the channel is closed and empty.
4️⃣ Waiting on Many with select
select is like a switch for channels: it blocks until one of its cases is ready, then runs that case. It's the heart of timeouts, fan-in, and cancellation patterns. If several cases are ready at once, Go picks one at random to keep things fair.
These lines send three numbers, close the channel, and range over it. Put them in the correct order:
B, E, A, F, G, C, D, H. Make the channel, then a goroutine that sends the three values and close s the channel (so the range can terminate), then range over it in main and print each value. Closing must happen or the range blocks forever.
Predict the output before revealing the answer.
Deadlock. The channel is unbuffered, so ch <- 1 blocks forever waiting for a receiver — but the receive is on the next line in the same goroutine, which never runs. Send from a separate goroutine, or make the channel buffered.
7 true — closing doesn't discard buffered values. You can still receive the 7 (and ok is true). Only once the buffer is drained does a receive give 0 false .
Panics: "send on closed channel". Once closed, a channel must never be sent to again. This is why only the sender should close, and only when it's truly done.
When the producer and consumer run at different speeds and you want the producer to keep working without waiting for each value to be picked up. Pick a capacity that matches your batch size; an oversized buffer just hides backpressure problems.
No. Close only when receivers need to know the stream is finished — typically so a range can stop. If nobody ranges and the channel is simply garbage-collected, you don't need to close it.
Use channels to pass ownership of data between goroutines ("communicate to share memory"). Use a Mutex when several goroutines must read and write one shared structure in place. Both are valid; pick the one that makes the data flow clearest.
Q: What does a select with a default case do?
It makes the select non-blocking: if no channel is ready, the default runs immediately instead of waiting. Handy for polling without blocking.
No blanks — just a brief. Feed numbers into a jobs channel, let a worker goroutine sum them, and send the total back on a done channel. This producer/worker/result shape is the backbone of real Go pipelines.
Practice quiz
How do you create an unbuffered channel of ints?
- make(chan int)
- make(chan int, 0)
- new(chan int)
- Both make(chan int) and make(chan int, 0)
Answer: Both make(chan int) and make(chan int, 0). make(chan int) and make(chan int, 0) both create an unbuffered channel — capacity zero means no buffer.
On an unbuffered channel, a send (ch <- v) blocks until...
- The buffer is full
- A receiver is ready to take the value
- The channel is closed
- It never blocks
Answer: A receiver is ready to take the value. An unbuffered channel is a rendezvous: the send blocks until a receiver is ready to receive.
What happens with: ch := make(chan int); ch <- 1; fmt.Println(<-ch) in one goroutine?
- Prints 1
- Deadlock
- Prints 0
- Panics
Answer: Deadlock. The unbuffered send blocks waiting for a receiver, but the receive is on the next line in the same goroutine — deadlock.
After ch := make(chan string, 2); ch <- "a"; ch <- "b", what is len(ch) and cap(ch)?
- len 2, cap 2
- len 0, cap 2
- len 2, cap 0
- len 1, cap 2
Answer: len 2, cap 2. Two values are buffered without blocking, so len is 2 and the capacity stays 2.
What does receiving from a closed AND drained channel with v, ok := <-ch give?
- The last value, true
- The zero value, false
- A panic
- Blocks forever
Answer: The zero value, false. Once a channel is closed and empty, receives return the zero value and ok is false.
Given c := make(chan int, 1); c <- 7; close(c); v, ok := <-c — what is v, ok?
- 0, false
- 7, false
- 7, true
- 0, true
Answer: 7, true. Closing doesn't discard buffered values, so you still receive the 7 with ok true; only once drained do you get 0, false.
What does sending on a closed channel do?
- Returns an error
- Silently drops the value
- Panics: send on closed channel
- Reopens the channel
Answer: Panics: send on closed channel. Sending after close panics with 'send on closed channel'. Only the sender should close, and only when done.
Why might a for v := range ch loop never end?
- The channel is buffered
- The channel was never closed
- The values are nil
- select is missing
Answer: The channel was never closed. A range over a channel only stops when the channel is closed. Forget to close it and the loop blocks forever.
What does select do?
- Runs every ready case
- Blocks until one case is ready, then runs that one
- Picks the first case in source order
- Loops over channels
Answer: Blocks until one case is ready, then runs that one. select waits until one of its cases is ready, then runs that case; if several are ready it picks one at random.
What does adding a default case to a select do?
- Makes it loop
- Makes it block longer
- Makes it non-blocking — runs default if nothing is ready
- Closes all channels
Answer: Makes it non-blocking — runs default if nothing is ready. A default case makes select non-blocking: if no channel is ready right now, default runs immediately.