Benchmarking

⭐ Interview Importance: HIGH
⏱️ Revision Time: 4 min

TL;DR

Go has benchmarking built directly into its testing framework (testing package). A Benchmark allows you to run a specific function millions of times to find its exact execution time (down to the nanosecond) and track how many times it forces the Garbage Collector to allocate memory on the heap.

Mental Model

How It Works

Benchmark functions live in _test.go files right alongside your normal unit tests.

  • The function name must start with Benchmark (e.g., BenchmarkStringConcat).
  • It accepts a pointer to testing.B.
  • It must contain a for i := 0; i < b.N; i++ loop.
  • The Go test runner dynamically adjusts b.N (running the loop 1 time, then 100 times, then 1,000,000 times) until the benchmark runs for at least 1 full second, ensuring statistical accuracy.

Example

// main_test.go
package main

import (
	"strings"
	"testing"
)

// A slow function using standard string concatenation (+)
func concatString(a, b string) string {
	return a + b
}

// A fast function using strings.Builder
func builderString(a, b string) string {
	var builder strings.Builder
	builder.WriteString(a)
	builder.WriteString(b)
	return builder.String()
}

func BenchmarkConcat(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = concatString("Hello", "World")
	}
}

func BenchmarkBuilder(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = builderString("Hello", "World")
	}
}

Run the benchmark:
go test -bench=. -benchmem

Output:

BenchmarkConcat-8    100000000   15.5 ns/op     10 B/op     1 allocs/op
BenchmarkBuilder-8    50000000   28.1 ns/op     16 B/op     1 allocs/op

(Wait, for tiny strings, + is actually faster! strings.Builder only wins when concatenating many strings in a loop).

Common Interview Questions

What does the -benchmem flag do?

It is the most important flag. It tells the test runner to track Heap Allocations. It adds B/op (Bytes allocated per operation) and allocs/op (How many distinct heap allocations occurred). In high-performance Go, reducing allocs/op to 0 is often more important than reducing raw nanoseconds, because fewer allocations mean less Garbage Collection pressure, which prevents latency spikes across the entire server.

How do you benchmark a function that has an expensive setup phase?

If your function needs to connect to a mock database before running the loop, you don’t want the setup time included in the benchmark metrics. Use b.ResetTimer() immediately before the b.N loop starts to reset the clock.