What does make(chan Sample, 100000) allocate at the moment the channel is created?
answer
- sized once, at make
- the ring exists before the first send
- capacity times element size
- queued copies keep their pointees alive
- no grow function exists
basics
~20 sIt allocates the channel's bookkeeping plus a ring buffer with room for 100000 Sample values immediately, before any send. Capacity is a reservation made at creation, not storage that grows as values arrive, and it cannot be changed afterwards.
solid answer
~50 sThe capacity argument is a reservation, not a limit that fills in lazily. `make(chan Sample, 100000)` allocates the channel header plus a ring buffer sized `100000 * unsafe.Sizeof(Sample{})` right there — for a 32-byte `Sample` that is roughly 3 MB committed before a single send. `make(chan Sample)` allocates only the header, since capacity 0 needs no ring. Two consequences follow. First, a generous capacity is not free just because the channel is usually empty. Second, sends copy the value into the buffer, so queued elements keep whatever they point at reachable: a buffer of structs holding large `[]byte` payloads pins all of that memory until the values are received. And the capacity is fixed for the channel's life — there is no resize, so a channel that turned out to be the wrong size has to be replaced.
code
go · 11 linestype Sample struct {
Name string
Value float64
At time.Time
}
// The ring buffer is allocated here, before a single send.
ch := make(chan Sample, 100_000)
fmt.Println(len(ch), cap(ch)) // prints: 0 100000
// Nothing in the language can raise or lower that 100000 later.go deeper
Know that the number you pass to make is decided once and reserves space straight away, and that there is no way to make a channel bigger later.
Be able to compute the cost as capacity times element size, explain that sends copy values into the ring, and say why an unbuffered channel needs no ring at all.
Show that you look for over-sized channels the way you look for any live allocation, and that you understand queued elements pinning their pointees until they are received.
Frame capacity as an immutable policy baked into code: unlike a pool size behind a setter, it cannot be retuned in production, so it belongs in the design review rather than in a runbook.
## Capacity is committed, not promised When you write `ch := make(chan Sample, 100000)`, the runtime allocates the channel's internal structure and, in the same step, a circular buffer with room for 100000 elements of type `Sample`. The memory is there before the first send. Nothing about a channel grows on demand: the number you pass to `make` is a reservation made at creation time. The size of that reservation is `capacity * size of the element type`. That is easy to underestimate because the element type is often a struct rather than a word: - `make(chan int, 100000)` — about 800 KB on a 64-bit build. - `make(chan Sample, 100000)` where `Sample` is a name, a float64 and a timestamp — on the order of a few megabytes. - `make(chan [4096]byte, 1000)` — about 4 MB, for a channel that may sit empty all day. An unbuffered channel, `make(chan Sample)`, allocates no ring at all: the runtime only needs the header, because values are copied directly from sender to receiver when the two meet. ## Elements are copies, and copies keep things alive A send copies the value into a buffer slot with ordinary Go value semantics. Two things follow. First, the buffer's own footprint is the flat size of the element type, not the size of everything the element refers to. A `Sample` containing a `string` occupies the string header in the slot; the bytes live elsewhere. Second — and this is the part that surprises people — those queued copies are **reachable**, so anything they point to is reachable too. If each queued struct holds a 1 MB `[]byte`, a buffer holding 500 undelivered values is pinning about 500 MB that the garbage collector cannot touch until a receiver takes them. A large buffer of pointer-bearing elements is a memory retention mechanism, not just a scheduling convenience. ## The capacity cannot be changed There is no `resize`, no grow-on-full, and no way to shrink a channel that was created too large. `cap(ch)` returns the same number for the channel's entire life. If a capacity turns out to be wrong, your options are to create a new channel and get producers and consumers onto it — a real coordination problem in a running system, since both sides hold the old value — or to restart the component with a different number. This is why capacity deserves more thought than it usually gets. It is a decision you make once, at construction, in code, and cannot retune the way you can retune a pool size behind a setter or a flag read at runtime. ## Practical guidance - **Size for the burst you must absorb, not for comfort.** A buffer that holds a second or two of production is a deliberate choice you can defend. A buffer of 100000 is usually a number nobody derived from anything. - **Account for the element type.** Ask what one element costs and multiply. A channel of large arrays or fat structs is a memory decision disguised as a concurrency decision. Sending a pointer instead reduces the ring to one word per slot — at the cost of the copies no longer being independent, so the sender must stop touching what it handed over. - **Remember that empty is not free.** A rarely used channel with a huge capacity still holds its ring. This shows up in a heap profile as a live allocation attributable to the `make` site, which is a clean way to find over-sized channels in a service that seems to use more memory than its workload suggests. - **Treat the number as part of the design.** Because it is immutable, the capacity encodes a policy: how much slack the producing side gets before it feels the consuming side at all.
- Can you grow or shrink a channel's buffer after it has been created?No. Capacity is fixed at `make` and `cap(ch)` returns the same value forever; there is no resize operation. Changing it means creating a new channel and getting every producer and consumer onto it, which in a running system usually means restarting the component that owns them.
- Why can a buffered channel hold far more memory alive than its element size suggests?Because sends copy the element, and the queued copies are reachable from the channel. If the element type contains pointers — a slice, a map, a string, a pointer field — everything they refer to stays reachable until a receiver takes the value. A buffer of a few hundred structs each holding a megabyte of bytes pins hundreds of megabytes.
- Does an unbuffered channel allocate a buffer?No. `make(chan T)` has capacity 0, so there is no ring to allocate — only the channel's own header with its wait queues and lock. Values are copied directly from the sending goroutine to the receiving one at the rendezvous, so nothing is ever stored in the channel itself.
The capacity is a warehouse you rent on day one, not shelving you add as goods arrive. You pay for the whole floor whether or not anything is stored in it, and you cannot extend the lease to a bigger floor without moving.
saying these in an interview costs you the question
- Says the buffer grows as values are sent
- Thinks capacity can be raised at runtime
- Assumes an unused large capacity costs nothing
- Believes the buffer stores pointers into the sender's memory
- Ignores element size when picking a capacity