What do the buf and all arguments to Go's runtime.Stack(buf, all) control?
answer
- the function does not allocate for you
- the return value is a count, not an error
- compare what you got with len(buf)
- one boolean turns a local read into a global pause
basics
~20 sruntime.Stack formats a stack trace into the caller-supplied buf and returns the number of bytes written. It never grows buf, so a short buffer is silently truncated. Passing all as true appends every other goroutine's stack and stops the world while doing so.
solid answer
~40 sThe signature is `runtime.Stack(buf []byte, all bool) int`. The trace is formatted into `buf`, and the return value is how many bytes were written; the function never allocates a larger buffer and never returns an error, so if the trace does not fit it is simply cut short. The way you detect that is `n == len(buf)` — that is the loop `runtime/debug.Stack` runs internally, starting at 1 KB and doubling until `n < len(buf)`. The `all` argument switches from just the calling goroutine to the calling goroutine followed by every other one, and collecting that consistent snapshot requires stopping the world for the entire walk and formatting pass. So `all=false` is cheap and local; `all=true` is a global pause whose length and output size both scale with the number of goroutines.
code
go · 10 linesfunc Stack() []byte {
buf := make([]byte, 1024)
for {
n := runtime.Stack(buf, false)
if n < len(buf) {
return buf[:n]
}
buf = make([]byte, 2*len(buf))
}
}go deeper
Recall the shape of the call: you pass a byte slice and a boolean, and you get back how many bytes were written. Know that runtime/debug.Stack wraps it for the common case.
Explain truncation and the n == len(buf) retry loop, and state clearly that the boolean switches between one goroutine and all of them with a stop-the-world pause.
Show you would size the buffer from the real goroutine count, detect truncation instead of shipping a clipped dump, and treat the all=true call as a deliberate, rate-limited operator action.
Own the guardrail: decide whether a whole-process stack capture is exposed at all in your services, who may trigger it, and what latency budget a pause proportional to goroutine count is allowed to consume.
## Signature and contract ```go func Stack(buf []byte, all bool) int ``` Three things about this contract catch people out, and all three follow from the fact that it is a runtime function designed to work even when the heap is in trouble. **It writes into your buffer.** `runtime.Stack` does not allocate a slice for you and hand it back. You supply the storage. That is deliberate: the function must be usable in situations where allocating is a bad idea, such as inside a signal-driven dump path or when you are already near a memory limit. **It returns a count, not an error.** The `int` is the number of bytes written. If the formatted trace is larger than `len(buf)`, the runtime writes what fits and stops. There is no error, no partial-write flag, and no marker in the text saying the trace was cut. A truncated trace looks exactly like a short trace, which is how people ship diagnostics that lose the frames they needed. **Detecting truncation is your job.** The convention is: if `n == len(buf)`, assume you were truncated, grow the buffer and try again. `runtime/debug.Stack` implements exactly this, and reading it is the fastest way to remember the rule: ```go func Stack() []byte { buf := make([]byte, 1024) for { n := runtime.Stack(buf, false) if n < len(buf) { return buf[:n] } buf = make([]byte, 2*len(buf)) } } ``` Note the two facts embedded there: the doubling loop, and the hardcoded `false`. `debug.Stack` is always the current goroutine only. ## What the all argument really costs With `all` false, the runtime walks one goroutine's frames — the caller's — and formats them. The goroutine is already stopped, since it is the one making the call, so nothing else in the program is disturbed. With `all` true, the output is the calling goroutine's trace followed by a trace for every other goroutine in the process. To produce a coherent snapshot the runtime has to stop the world: no goroutine may run while stacks are being walked, or the walk would read frames that are actively changing. Critically, the world stays stopped for the entire operation, formatting included, not just for a quick census. Two consequences follow directly: - **Pause time scales with goroutine count.** A program with a few dozen goroutines will not notice. A service holding tens or hundreds of thousands of goroutines can pause for a very long time on human scales, and every in-flight request pays that latency. - **Output size scales too.** Each goroutine contributes a header line and a frame list. Multiply by the goroutine count and the buffer you need is measured in megabytes, which you must either size for or accept truncation of. That combination is why `all=true` is an operator-triggered, rate-limited action rather than something a service does on a timer or on every error. ## The formatted output Each goroutine is introduced by a header naming its id and its state — running, `chan receive`, `select`, `IO wait`, `sync.Mutex.Lock`, `sleep` and so on — sometimes with how long it has been blocked. Then come frame lines in pairs: the function with its arguments as raw words, then a tab-indented file path and line number. It is the same layout the runtime prints when a program dies from an unrecovered panic, which is why it reads as familiar to anyone who has debugged Go at all. ## Choosing a buffer For the single-goroutine case, do not invent a size — call `debug.Stack()` and let the standard library's doubling loop do it. Reach for `runtime.Stack` directly only when you need `all=true`, or when you must reuse a pre-allocated buffer because you cannot afford to allocate at that moment. In the `all=true` case, size from your actual goroutine count with headroom, check `n == len(buf)`, and grow-and-retry rather than shipping a trace that quietly ends mid-goroutine. Bear in mind that each retry pays the stop-the-world cost again, so starting far too small is worse here than it is for a single goroutine.
- How do you know the trace runtime.Stack gave you was truncated?By comparing the returned count with the buffer length. If `n == len(buf)`, the runtime filled the buffer exactly and may have had more to write, so treat it as truncated: allocate a bigger buffer and call again. There is no error and no marker in the text.
- Why is a fixed 64 KB buffer a poor default when all is true?Because output grows with goroutine count. A service with tens of thousands of goroutines produces megabytes of text, so 64 KB captures the first handful of goroutines and drops the rest — and nothing in the output tells you which ones were lost or how many there were.
- What does runtime/debug.Stack add on top of runtime.Stack?Buffer management, and nothing else. It runs the doubling loop so you always get a complete trace, and it passes all as false, so it is strictly the current goroutine. If you need other goroutines you must call runtime.Stack yourself.
saying these in an interview costs you the question
- Thinking runtime.Stack allocates a correctly sized slice for you
- Expecting an error or a panic when the buffer is too small
- Treating all=true as a cheap memory read
- Assuming all=true covers only goroutines you started yourself
- Reading the return value as a goroutine count