Skip to content

Day 058 — Channels: unbuffered vs buffered

Month 3 · Week 1 · ⬅ Day 057 · Day 059 ➡ · Journal index

🎯 Learning Objective

Use channels to communicate between goroutines, and choose unbuffered vs buffered with intent.

📚 Topics

  • make(chan T) vs make(chan T, n); send/receive blocking semantics
  • The rendezvous (synchronisation) property of unbuffered channels
  • close, range over a channel, len/cap

📖 Reading / Sources

📝 Notes

  • "Don't communicate by sharing memory; share memory by communicating." Channels move ownership of data between goroutines → [[channels]].
  • Unbuffered (make(chan T), cap 0): a send blocks until a receiver is ready and vice-versa — they rendezvous. This is a synchronisation point, not just data transfer → [[unbuffered-channel]].
  • Buffered (make(chan T, n)): send blocks only when the buffer is full; receive blocks only when empty. It decouples producer and consumer by up to n items → [[buffered-channel]].
  • len(ch) = items currently buffered; cap(ch) = buffer size. For unbuffered both reasoning points are 0.
  • close(ch) signals "no more values." Receivers can keep draining buffered values after close; once drained, receives return the zero value with ok == false. for v := range ch exits when the channel is closed and drained → [[channel-axioms]].
  • Closing is the sender's job and must happen exactly once — see Day 059 for the axioms.
  • chan struct{} is the idiomatic zero-size signal channel (pure notification, no payload).

💻 Code Examples

nums := make(chan int)
go func() {
    defer close(nums) // sender owns the close
    for i := 0; i < 5; i++ {
        nums <- i * i
    }
}()
sum := 0
for n := range nums { // ends when nums is closed & drained
    sum += n
}
fmt.Println(sum) // 30

Full code: examples/month-03/channels/main.go · Run: go run ./examples/month-03/channels

🏋️ Exercises / Practice

Exercise Status Link
Channel pipeline: Gen → Map → Filter → Collect exercises/month-03/week-1/pipeline/
Worker pool over a jobs channel exercises/month-03/week-1/workerpool/

🐛 Mistakes Made

  • Sent on a buffered channel past its cap with no receiver → blocked forever (deadlock).
  • Closed a channel from the receiver side; panicked when the sender later closed it again.

❓ Open Questions

  • Is a buffered channel ever a substitute for a WaitGroup? (Sometimes, e.g. a counting semaphore, but they express different intents.)

🧠 Active Recall (answer without looking)

  1. Q: On an unbuffered channel, what happens when you send and no one is receiving?

    A The send blocks until some goroutine receives — the send and receive rendezvous at the same instant.

  2. Q: What does v, ok := <-ch give after ch is closed and drained?

    A `v` is the element type's zero value and `ok` is `false`.

🪶 Feynman Reflection

An unbuffered channel is a direct handoff. Think of it as passing a baton: you can't let go until a teammate grabs it (rendezvous). A buffered channel is a short conveyor belt — you can drop a few items and walk away, until the belt fills up.

🕳️ Knowledge Gaps

  • Performance trade-offs of buffer sizing under bursty load — measure later.

✅ Summary

I can move data between goroutines with channels and pick unbuffered (sync) vs buffered (decouple) deliberately, closing and ranging correctly.

⏭️ Next Steps / Prep for Tomorrow

  • Day 059: the precise channel axioms for close, nil, and direction.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: feat(examples): unbuffered vs buffered channels (day 058)