Theory

What the counter cannot see

The same command prints a second part:

keeping only the LAST item of a 100000-item queue:
q = q[1:] (header points into array)       7034 KB still on the heap
copied the survivor out                       0 KB still on the heap

One item is left out of a 100,000-item queue. With q = q[1:], seven megabytes are still in memory.

Why

q[1:] does not make a new array. It returns a header pointing at the same array, one place further along. After removing 99,999 items you hold a one-element slice — whose pointer still lands in the middle of a hundred-thousand-element array.

A garbage collector cannot free part of an array. The array is reachable or it is not. One live element keeps all of it.

That is why copied the survivor out reads zero: copy the surviving item into a fresh slice and the old array becomes unreachable and goes away.

What this result actually means

In lesson 1 we said: count operations, not seconds, because a count is exact, deterministic and machine-independent. All of that is true.

But a counter measures time, not memory — and here it reported zero for the option that leaks seven megabytes.

An operation count is the right instrument for time. For memory it is blind.

That is not a flaw in the counter. It is its boundary, and now you know where it is. From here on, when you compare structures, ask both questions.

Why the ring buffer

Ring moves zero elements and holds nothing: r.buf[r.head] = Item{} lets the item go the moment it is handed over. One fixed-size array that never moves and never grows.

The price is that you must know the size up front. That is a trade, not a win.

Gotcha

This leak is not a queue problem. Any time you keep a small piece of a big slice — first := data[:10], tail := log[len(log)-5:] — you are holding the whole array. If the piece outlives the original, copy it: out := append([]Item(nil), data[:10]...).