TRT
Sign in
easyCoredebugging

Walk through the internal structure of a Go channel (`hchan`), explain the exact table of channel state behaviors (nil vs open vs closed for send, receive.

Detail `hchan` (`buf`, `sendx`, `recvx`, `sendq`, `recvq`, `lock`), `sendDirect` stack-to-stack copy, the nil/open/closed matrix, and buffered (`cap=1`) vs `select`+`ctx.Done()` fixes.

TRT practice prompt — not a verified question from a named employer.

The Runtime Theory Team2 min read

A strong answer

A Go channel make(chan T, cap) allocates a runtime.hchan struct on the heap containing: a circular ring buffer buf of cap slots (sendx, recvx, qcount), a receive wait queue recvq of blocked reader goroutines (sudog), a send wait queue sendq of blocked writer goroutines (sudog), a closed flag, and a lightweight internal mutex (lock). Look at the three exact cases when Goroutine G1 executes ch <- val: (1) Receiver already waiting (recvq non-empty): Go pops the waiting reader sudog from recvq and goreadys it after copying val DIRECTLY from G1's stack into the waiting reader G2's stack (sendDirect)—completely bypassing hchan.buf and the channel lock on wakeup! (2) Buffer space available (qcount < dataqsiz): G1 copies val into buf[sendx], increments sendx and qcount, and continues immediately without blocking. (3) Buffer full (or unbuffered cap == 0 with no receiver): G1 allocates a sudog, enqueues itself in sendq, and calls gopark—putting G1 to sleep off-CPU until some receiver reads from ch (or close(ch) panics it). If ch is abandoned with no reader, G1 never wakes up (goroutine leak)!

A goroutine parked in hchan.sendq or hchan.recvq via gopark is NOT garbage-collected even if no other live variable references the channel outside that goroutine; it remains parked in RAM until another goroutine performs the matching channel operation or the process exits. Because the Go runtime scheduler holds active references to all parked g structs waiting on sudog descriptors, a goroutine blocked on ch <- v where no other active goroutine holds ch can never be woken and is never collected by GC.

Creating a buffer of make(chan Result, 1) (capacity 1) for a single-result child goroutine costs only sizeof(Result) extra bytes while guaranteeing that ch <- res never blocks even if the parent timed out and left! In production systems (Go runtime/chan.go (chansend, chanrecv, sendDirect), net/http request cancellation loops, and uber-go/goleak unit-test goroutine leak detectors.), engineers monitor these exact trade-offs to keep p99 latency predictable and prevent resource exhaustion under peak load.

What the interviewer is listening for

  • Traces how Go's runtime.hchan channel internals and goroutine leak prevention operates in memory step by step rather than giving a vague textbook definition.
  • States the core correctness invariant and explains why edge cases do not violate it.
  • Quantifies time, space, and hardware costs (Uncontended Channel Send/Recv, Goroutine Stack Creation) with concrete numbers.
  • Identifies real-world failure modes (Spawning go func() { ch <- slowWork() }() on an unbuffered make(chan T) when the parent can exit early on timeout/error) and explains how production systems mitigate them.

Common mistakes

  • Treating Go's runtime.hchan channel internals and goroutine leak prevention as a black box without explaining the underlying memory or state transitions.
  • Ignoring worst-case tail latency or memory overhead (Closing a channel from the receiver side (or from one of N concurrent senders)).
  • Forgetting boundary conditions and error recovery (Spinning in for { select { case x := <-closedCh: ... } } without checking the , ok boolean!).

This answer walks

Practice follow-ups

  1. 01Who should call `close(ch)` on a channel—the sender or the receiver—and how do you safely close a channel when there are multiple concurrent senders?
  2. 02Why does a Go channel still use an internal mutex (`hchan.lock`) inside `runtime/chan.go` despite the proverb 'Share memory by communicating'?

Not started

Sign in to save your learning progress.

Sign in to save