In Go, what does `buf = buf[:0]` reuse, and what stays in the underlying array?
answer
- the array does not move
- only one of the three words changes
- old bytes are still addressable
- whoever holds the old slice sees the new data
basics
~20 sbuf[:0] keeps the same backing array, pointer and capacity and only sets the length to 0, so later appends overwrite from index 0 with no new allocation. The old bytes stay in the array; nothing is zeroed or freed.
solid answer
~50 sA slice is a pointer, a length and a capacity, and `buf[:0]` rewrites only the length. The array is untouched, so appending afterwards writes from index 0 into memory that is already allocated — which is why the idiom removes an allocation per iteration in a loop that formats one record at a time. What it does not do is clear anything: the previous bytes are still in the array past the new length, and a slice may be resliced up to its capacity, so `buf[:cap(buf)]` still shows them. That matters in two ways. Any slice you handed to someone else still points at that array and will read whatever you refill it with, and the array stays alive as long as any slice of it is reachable. `buf = nil` is the opposite move: it drops the array so the next append allocates a fresh one.
code
go · 8 linesbuf := make([]byte, 0, 16)
buf = append(buf, "secret"...)
buf = buf[:0] // same array, same capacity, length 0
buf = append(buf, "hi"...)
fmt.Println(len(buf), cap(buf)) // prints: 2 16
fmt.Println(string(buf[:6])) // prints: hicretgo deeper
Know that buf[:0] keeps the same array and only sets the length to zero, and that appending afterwards starts writing at index 0 again with no new allocation.
Explain the slice header — pointer, length, capacity — and why nothing is cleared: the bytes past the new length are still present and still readable by reslicing up to the capacity.
Treat the aliasing as the real risk. A slice already handed to a caller points at the array you are about to refill, which is how one response ends up carrying bytes from another piece of work.
Be able to say where reuse belongs at all: which layer owns the buffer, whether any API may return a value backed by it, and what the team's rule is for buffers that ever held sensitive data.
## The three-word header A slice is a pointer to a backing array, a length, and a capacity. `buf[:0]` produces a new slice header with the **same pointer**, the **same capacity**, and a length of 0. No memory is allocated, no memory is freed, and not a single byte of the array is touched — reslicing is arithmetic on three words. ## What that buys you Because the pointer and capacity survive, the next `append` writes into the array that is already there, starting at index 0. In a loop that formats one record per iteration, this is the difference between one allocation for the whole loop and one allocation per iteration: ``` buf := make([]byte, 0, 256) for _, rec := range records { buf = buf[:0] buf = appendRecord(buf, rec) w.Write(buf) } ``` After a few iterations the buffer has reached the high-water mark of the records it has seen and stops growing entirely. The same idea is what `bytes.Buffer.Reset` and `strings.Builder.Reset` do for those types: keep the storage, drop the contents. ## What it does not do **It does not clear anything.** The bytes past the new length are still in the array, and they are still reachable — a slice may be resliced up to its capacity, so `buf[:cap(buf)]` shows them: ``` buf := make([]byte, 0, 16) buf = append(buf, "secret"...) buf = buf[:0] buf = append(buf, "hi"...) // buf[:6] is "hicret" - only the first two bytes were overwritten ``` Three consequences follow. **1. Aliasing.** Any slice header that already points into that array keeps pointing into it. If you returned `buf[:n]` to a caller and then reset and refilled `buf`, the caller's slice now reads the *new* bytes. Nothing panics and nothing warns; the caller simply sees data that belongs to someone else's work. This is exactly how a reused buffer bleeds one request's output into another's response, and it is why a function that borrows a shared buffer must copy before handing anything out (`append([]byte(nil), buf...)` or `string(buf)`, both of which copy). **2. Retention.** The array stays alive as long as *any* slice of it is reachable. Holding `buf[:8]` out of a 4 MB buffer keeps all 4 MB from being collected. **3. Secrets are not erased.** Resetting the length hides bytes from `len`-respecting code, nothing more. To actually overwrite them, zero the storage explicitly — `clear(buf[:cap(buf)])` — before reusing it. Even that is a weak guarantee: if the buffer ever grew, the abandoned older arrays still hold copies until the collector reuses that memory. ## `buf = buf[:0]` versus `buf = nil` They are opposites in intent: - `buf = buf[:0]` — keep the array, keep the capacity, reuse. Length 0, capacity unchanged. Later appends are allocation-free up to the capacity. - `buf = nil` — drop the reference. Length 0, capacity 0, the array becomes garbage, the next append allocates a fresh one. This is what you want when the buffer grew huge for one unusual item and you do not want to hold that memory. There is no third option that keeps a *smaller* part of the array: a slice's capacity always runs to the end of the array it points into, unless you cut it explicitly with the three-index form. ## When reuse is the wrong tool Reuse introduces a lifetime rule where none existed: whoever refills the buffer must know that nobody else is still reading it. In a single function that rule is trivially checkable. Across a package boundary, across a goroutine, or once the buffer is handed to something that stores it, the rule is invisible at the call site and will eventually be broken by someone who never saw it. Confine reuse to a scope where you can see both ends of it. ## What an interviewer is listening for That you describe the header rather than saying "it empties the slice"; that you volunteer that nothing is zeroed; and that you can name the aliasing failure — a caller holding a slice of a buffer you are about to refill — as the reason this idiom needs care rather than as a curiosity.
- How is `buf = buf[:0]` different from `buf = nil`?`buf[:0]` keeps the pointer and the capacity, so the array stays alive and the next appends are allocation-free. `buf = nil` drops the reference: length and capacity become 0, the array becomes garbage, and the next append allocates a new one. Use the first to reuse the memory, the second to stop holding memory you no longer want.
- If the buffer held sensitive bytes, what actually erases them?An explicit overwrite — `clear(buf[:cap(buf)])`, or a loop assigning the zero value — before you reuse or release it. Resetting the length only hides the bytes from code that respects `len`. Even the overwrite is a weak guarantee, because if the buffer ever grew, older abandoned arrays still hold copies until that memory is reused.
- Where does bytes.Buffer fit into this idiom?`bytes.Buffer.Reset` does the same job for a Buffer: it keeps the storage it has already allocated and sets the length to zero, so a Buffer reused across iterations stops allocating once it reaches its high-water mark. `Buffer.Cap` reports how much storage it is holding, which is what you check when deciding whether it has grown too large to keep.
Resetting to buf[:0] is like moving the 'written up to here' marker on a whiteboard back to the top without wiping the board. The next writing covers some of the old text; the rest is still there to read.
saying these in an interview costs you the question
- Says buf[:0] frees or zeroes the backing array
- Thinks the next append allocates a fresh array anyway
- Assumes a slice returned to a caller is a copy
- Uses buf[:0] to scrub secret bytes from memory
- Cannot explain why buf[:cap(buf)] is legal