In a Go benchmark func BenchmarkX(b *testing.B), what is b.N and who sets its value?
answer
- you do not choose the count
- the whole function is called more than once
- grow until the run fills the benchmark duration
- elapsed divided by the count is ns/op
- nothing runs without the -bench flag
basics
~20 sb.N is the iteration count the testing package hands a benchmark; you never set it. go test calls the whole function repeatedly with a growing b.N until the run reaches -benchtime, then reports elapsed time divided by b.N.
solid answer
~40 sA benchmark is a function named `BenchmarkXxx(b *testing.B)` in a `_test.go` file, and its body must repeat the operation exactly `b.N` times: `for i := 0; i < b.N; i++ { ... }`. You never assign `b.N` — the testing package does. It calls the whole function with `b.N = 1`, measures, estimates how many iterations it would take to fill the benchmark duration (`-benchtime`, default 1 second), then calls the function again with a larger `b.N`, repeating until the run is long enough. Only that last, longest run is reported, as `ns/op` = elapsed time divided by `b.N`. Two consequences bite beginners: everything before the loop runs on every attempt, and benchmarks are skipped entirely unless the `-bench` regexp matches, so you need `go test -bench=.`.
code
go · 6 linesfunc BenchmarkEncodeRecord(b *testing.B) {
rec := Record{ID: 42, Body: []byte("payload")}
for i := 0; i < b.N; i++ {
encodeRecord(rec)
}
}go deeper
Be ready to write the five-line skeleton from memory: Benchmark prefix, *testing.B parameter, a loop bounded by b.N, and go test -bench=. to run it. Say plainly that the harness sets b.N, not you.
Explain the ramp-up: the whole function is re-invoked with a larger b.N until the run fills -benchtime, and only that last run is reported. Point out that ns/op is simply elapsed time divided by b.N.
Show that you know what the ramp-up does to setup code sitting above the loop, and that a body whose work depends on b.N produces a meaningless figure. Interviewers listen for whether you have actually read benchmark output in anger.
Frame where benchmark numbers belong in a team's workflow: which operations are worth a permanent benchmark, what the noise floor of your CI machines is, and why a single ns/op figure alone is rarely enough to justify a change.
## The shape A Go benchmark lives in a `_test.go` file next to the code and looks like this: ```go func BenchmarkEncodeRecord(b *testing.B) { rec := Record{ID: 42, Body: []byte("payload")} for i := 0; i < b.N; i++ { encodeRecord(rec) } } ``` The rules are mechanical: the name must start with `Benchmark`, the single parameter is `*testing.B`, and the operation you want to measure must run exactly `b.N` times. ## What b.N is `b.N` is a plain `int` field on `testing.B` that the harness fills in before it calls your function. It is the number of repetitions of the operation for this measurement attempt. It is **not** a size, **not** a concurrency level, and **not** something you assign. ## How the harness picks it The testing package cannot know in advance whether one iteration of your code takes 3 nanoseconds or 3 milliseconds, so it discovers that by running the benchmark: 1. It calls the function with `b.N = 1` and times it. 2. If the run was shorter than the benchmark duration (`-benchtime`, default `1s`), it estimates how many iterations would fill that duration, grows `b.N` towards that estimate (rounded up to a readable number, and never by more than a large fixed factor in one step), and calls the function **again from the top**. 3. It repeats until a run lasts at least `-benchtime`. Only the final run produces the reported numbers. Everything from earlier attempts is discarded. The key thing juniors miss is step 2's phrase *from the top*: the harness re-invokes the **entire function**, not just the loop. Any setup you wrote above the loop runs once per attempt, and — crucially — it runs inside the timed region of the final, reported attempt unless you tell the harness otherwise. ## Reading the output ```text $ go test -bench=EncodeRecord goos: linux goarch: amd64 BenchmarkEncodeRecord-8 2874316 412.6 ns/op PASS ``` * `-8` is the value of `GOMAXPROCS` the benchmark ran with, not a CPU id. * `2874316` is the final `b.N`. * `412.6 ns/op` is the elapsed time of that final run divided by `b.N`. Because `ns/op` is always *elapsed divided by b.N*, the number is only meaningful if the loop really performed `b.N` units of the same work. A body that ignores `b.N` and does a fixed amount of work reports a figure that shrinks as the harness raises `b.N` — pure nonsense. ## Benchmarks do not run by default `go test` runs `Test` functions and skips benchmarks. They execute only when the `-bench` flag's regexp matches: ```text go test -bench=. # all benchmarks in the package (tests run too) go test -bench=Encode -run=^$ # only matching benchmarks, no tests ``` `-run=^$` is the idiomatic way to say "no test name matches", which keeps the test suite from adding noise and time to a benchmarking run. ## The classic misuse of b.N ```go data := make([]byte, b.N) // wrong: input size must not depend on b.N ``` Sizing the input by `b.N` means each iteration does more work as the harness increases `b.N`, so the harness's estimate never converges on a stable per-operation cost and `ns/op` describes nothing. Input size is a constant of the benchmark (or a dimension you vary with sub-benchmarks); `b.N` is only the repetition count. Similarly, do not try to "help" by assigning `b.N` yourself or by breaking out of the loop early: the reported figure divides by the `b.N` the harness chose, so a loop that runs fewer iterations reports an artificially cheap operation. ## Failing inside a benchmark `testing.B` embeds the same failure API as `testing.T`: `b.Fatal`, `b.Fatalf`, `b.Error`, `b.Skip`, plus `b.Helper` and `b.Cleanup`. Use them for setup that can fail; a benchmark that panics or fails is reported as a failure rather than producing a number. ## Determinism when you need it If you want a fixed iteration count rather than a fixed duration — for debugging the benchmark itself, or when a single operation is very slow — pass an `x`-suffixed benchmark duration: `go test -bench=Encode -benchtime=100x` sets `b.N` to exactly 100.
- Why does a plain `go test` print no benchmark results at all?Benchmarks run only when the `-bench` flag's regexp matches their name; with no flag, nothing matches and only `Test` functions run. Use `go test -bench=.` for all of them, and add `-run=^$` when you want the benchmarks without the test suite running alongside them.
- What breaks if a benchmark sizes its input with `make([]byte, b.N)`?Each iteration then does a different amount of work, growing as the harness raises `b.N`. The reported `ns/op` is elapsed time divided by `b.N`, so it no longer describes a fixed operation and cannot be compared across runs. Keep the input size constant and let `b.N` count repetitions only.
- How do you force an exact number of iterations instead of a time-based run?Pass an `x`-suffixed benchmark duration: `go test -bench=Encode -benchtime=100x` sets `b.N` to exactly 100 and skips the harness's ramp-up. It is useful when a single operation is slow, or when debugging the benchmark itself, but a fixed small count gives a noisier measurement than a time-based run.
It is like timing a lap you can barely measure: instead of one lap, the coach keeps sending you out for more laps until the stopwatch reading is long enough to trust, then divides.
saying these in an interview costs you the question
- Says the benchmark author assigns b.N before the loop
- Thinks the benchmark function body is executed only once
- Reads ns/op as the total wall time of the run
- Expects go test with no flags to run benchmarks
- Sizes the input data with b.N instead of keeping it fixed