Under The Hood
backend go Go 1.21+

Go channels: the hchan struct and lock-based design

Last updated
Prerequisites:
Go goroutines and channels basics
Understanding of mutexes and condition variables
  • go
  • channels
  • hchan
  • concurrency
  • synchronization
  • runtime

Read at your depth

The practical view

ch := make(chan int, 3) creates a buffered channel. Sends and receives block when the buffer is full/empty respectively; unbuffered channels (make(chan int)) block until the other side is ready, making them synchronization points. close(ch) signals 'no more sends'; ranging (for v := range ch) drains until close. The primary use cases: hand-off synchronization between goroutines, work queues (buffered channels + workers), and graceful shutdown signals (close(ch) as a broadcast). select multiplexes over multiple channels: select { case v := <-ch1: ...; case ch2 <- v: ...; default: ... }.

Legacy vs modern

Unbuffered handoff vs buffered work queue

Unbuffered channels synchronize two goroutines with a rendezvous; buffered channels decouple producers from workers and need explicit close for completion.

before → after
Unbuffered broadcast (blocking main)
done := make(chan struct{}) // ❌ OLD: unbuffered → rendezvous, main blocks on send
go func() { work(); done <- struct{}{} }()
<-done // blocks until the goroutine sends
Buffered queue + close broadcast
jobs := make(chan int, 10) // ✅ NEW: buffered(10) + close(jobs) broadcasts completion
// N workers; close(jobs) broadcasts completion, workers drain

Interview gotchas

Under The Hood — a multi-depth technical interview hub.

Press ⌘ K to search.