What does the `//go:noinline` directive do, and when would you deliberately add one?
answer
- a comment the compiler actually reads
- position and spacing both matter
- it forbids, it never requests
- an instrument for an experiment, not a fix
- put it on, re-run the benchmark, take it off
basics
~20 s//go:noinline is a compiler directive on the line immediately above a function declaration, no space after the slashes. It forbids inlining that function anywhere. Its honest use is experimental: turning inlining off to see what it was worth.
solid answer
~50 sIt is a compiler directive, not a comment: `//go:noinline` on the line directly above a `func` declaration — no space between `//` and `go:` — tells the compiler never to inline that function, whatever its cost. `go build -gcflags=-m` then reports it on a `cannot inline` line naming the directive. You add one to run an experiment: if you believe an allocation disappeared because a helper got inlined, put the directive on that helper, re-run the benchmark with `-benchmem`, and see whether the allocation comes back. It is also useful to keep a function as its own frame while you are reasoning about a stack trace. What it is not is a fix — it makes code slower by construction — so it belongs in a scratch branch or, rarely, in a benchmark, with a comment saying why. To turn inlining off for the whole build instead, pass `-gcflags=all=-l`.
code
go · 9 lines// Temporarily forbidding inlining here to see what it was worth.
//go:noinline
func appendVarint(dst []byte, v uint64) []byte {
for v >= 0x80 {
dst = append(dst, byte(v)|0x80)
v >>= 7
}
return append(dst, byte(v))
}go deeper
Remember the exact shape: //go:noinline on its own line right above the func, no space after the slashes, and it forbids inlining rather than requesting it.
Explain why forbidding inlining can make a value heap-allocate — inlining is what lets escape analysis see the callee's body in the caller's frame — and know that -gcflags=all=-l is the whole-build equivalent.
Show the experimental discipline: state the hypothesis, add the directive, re-run the benchmark with -benchmem, compare, remove it. An interviewer is listening for someone who proves a compiler behaviour instead of asserting it.
The judgment to own is what may be committed: directives and compiler-shaped code are debt that outlives the person who added them, so decide where such experiments are allowed to live and what has to be written down beside them.
## Directives, not comments Go has no pragma syntax, so the compiler reads instructions out of specially shaped comments. A directive is a `//`-comment whose text begins with `go:` **with no space after the slashes**, placed on a line of its own immediately before the declaration it applies to: ```go //go:noinline func appendVarint(dst []byte, v uint64) []byte { ... } ``` Write `// go:noinline`, leave a blank line between the directive and the `func`, or attach it to the wrong declaration, and it silently becomes an ordinary comment. Nothing warns you. The reliable check is to rebuild with `-gcflags=-m` and confirm the compiler now reports the function on a `cannot inline` line naming the directive: if it still says `can inline`, your directive is not attached. ## What it does `//go:noinline` removes the function from inlining consideration entirely, regardless of how cheap its body is. Every call to it stays a real call, in every package. That is the whole effect — it does not change the function's semantics, its escape behaviour in isolation, or anything about how it is compiled beyond the substitution decision. The knock-on effects are what make it useful. Because inlining is what lets escape analysis see a callee's body in the caller's frame, forbidding it can turn a value that was living on the stack into a heap allocation. That is a cost in production and a *measurement instrument* in an experiment. ## The legitimate uses **Testing an inlining hypothesis.** This is the main one. You have a hot function; `-gcflags=-m` says a small helper it calls is inlined; you suspect that inlining is the reason `allocs/op` is zero. Add `//go:noinline` to the helper, re-run `go test -bench=BenchmarkEncode -benchmem`, and watch. If the allocation appears and the time per operation jumps, you have proven the causal link rather than assumed it. Then delete the directive. This is an A/B experiment on the compiler, and it is a far more direct answer than staring at `-m` output. **Keeping a frame visible.** While reasoning about a panic's stack trace or an unfamiliar call path, forcing a helper to remain a real frame can make the trace easier to follow. The Go runtime does reconstruct inlined frames in tracebacks and profiles, so this is a convenience rather than a necessity — and if you are stepping through code in a debugger, disabling inlining for the whole build is the usual approach instead. **Protecting a microbenchmark's subject.** If the function under measurement is small and its result unused, an aggressive compiler can inline it and then delete the work, and your benchmark measures an empty loop. Forbidding inlining is one blunt way to stop that — though it also means you are no longer measuring what production runs, which is exactly the flaw such benchmarks are famous for. ## What it is not It is not a performance fix. Adding `//go:noinline` to production code makes that code slower, by construction, and any allocation it introduces is real. If you find one in a codebase, the question to ask is what experiment left it behind, and whether the answer was ever written down. It is also not a stability guarantee for anything except inlining. If you want the compiler to leave a whole build unoptimised — for a debugger, say — that is `-gcflags` at build time, not directives sprinkled through source. ## Related switches and directives - `-gcflags=all=-l` disables inlining for every package in the build. This is the global version of the same experiment, and it is useful to bound the total contribution of inlining: build both ways, benchmark both, and see how much of your throughput the inliner is responsible for. - `//go:noescape` is a different and far more dangerous directive: it applies only to a function declared without a Go body (an assembly implementation) and *asserts* to the compiler that none of the pointers passed in escape. The compiler believes it without checking. Getting it wrong produces memory corruption, not a slow program. - `//go:inline` does not exist. You cannot demand inlining; you can only make the function cheap enough that the compiler chooses it, or forbid it with `//go:noinline`. ## Hygiene If a directive does have to survive in a committed benchmark or test, put an ordinary comment next to it explaining the experiment, because the directive line itself cannot carry prose — the compiler parses that line, and anything you add to it stops it being a directive. Then verify with `-gcflags=-m` that it is doing what you think, since a mis-typed directive is indistinguishable from a comment at every stage except that one output.
- Is there a `//go:inline` directive to force inlining?No. Go deliberately offers no way to demand inlining; the compiler's cost heuristic decides. Your only levers are making the function cheap enough to qualify — usually by splitting the cold path out — and `//go:noinline` to forbid it. If you need to know whether a call was inlined, `go build -gcflags=-m` tells you per call site.
- How would you measure what inlining is worth across a whole program rather than one function?Build and benchmark twice, once normally and once with `-gcflags=all=-l`, which disables inlining for every package including the standard library. The gap bounds the inliner's total contribution to that workload. It is a diagnostic, not a configuration you would ship — but it quickly tells you whether inlining is where the remaining headroom is.
- What does `//go:noescape` do, and why is it dangerous?It goes on a Go declaration with no body — one implemented in assembly — and asserts that no pointer passed to it escapes. The compiler takes that as fact without verifying it, and can then keep arguments on the stack. If the assertion is false, the runtime will eventually reuse memory that is still referenced, producing corruption rather than a diagnostic. It belongs in low-level code with assembly, not in application code.
saying these in an interview costs you the question
- Writes // go:noinline with a space and expects it to take effect
- Puts the directive inside the function body or a line away from the declaration
- Claims a matching //go:inline directive exists
- Leaves the directive in production code as a performance fix
- Confuses //go:noinline with //go:noescape