Prompt and context
The Go 1.26 release announcement reports roughly 30% lower baseline cgo overhead and more cases where the compiler puts slice backing stores on the stack. The interview is not a percentage quiz; it tests how you control call frequency, memory ownership, error propagation, and reproducible experiments across Go and C.
What the interviewer evaluates
- Whether you explain fixed cgo costs and thread, scheduler, and pointer rules.
- Whether you reduce boundary crossings with batching, long-lived C calls, or data layout.
- Whether you define memory ownership, lifetime, concurrency, and cancellation semantics.
- Whether benchmarks, profiles, and production metrics prove gains rather than one lucky run.
Questions to clarify first
Confirm whether C calls are tiny and frequent or long-running batches, whether data size and format are stable, whether copying is acceptable, and whether the C library is thread-safe. Clarify platforms, compiler, CGO_ENABLED, race detection, and rollback options.
A 30-second answer
I would define a boundary and baseline before optimizing call shape. Batch tiny frequent calls, specify Go/C ownership and error conversion, and use workers or a long-lived context for long tasks. Benchmarks measure end-to-end latency, throughput, allocation, CPU, and tail latency while comparing Go 1.25 and 1.26 on representative hardware and flags. A production canary confirms copying or lock contention did not erase the gain.
Step-by-step deep dive
1. Draw the FFI boundary
Wrap C capabilities in a few coarse-grained functions instead of crossing the boundary inside a Go loop. Pass length-delimited buffers, handles, and status codes; do not pass complex objects containing Go pointers directly to C. Long tasks can keep resources in a C context while Go waits or polls for results.
2. Control memory and lifetime
Define who allocates and frees memory and whether C may retain a pointer. Record byte counts and direction for copies; for zero-copy paths, verify alignment, read-only behavior, and lifetime. Every C allocation needs a symmetric release path, including errors. A slice conversion does not make a cross-language pointer safe.
3. Design concurrency and cancellation
Confirm C thread safety and global locks, then cap workers so many goroutines do not enter a serial C critical section. Cancellation must reach the C API. If the library cannot interrupt, isolate calls in reclaimable workers or a process boundary with timeouts and resource limits.
4. Build an evidence chain
Fix inputs and warm-up behavior with go test -bench, then use CPU, memory, and blocking profiles to locate boundary cost. Compare Go 1.25 and 1.26 with the same compiler, linker flags, and hardware across single-call, batched, and end-to-end tests. A canary watches p95/p99, crashes, C errors, and allocation changes, with a rollback switch.
Example of a strong answer
I would not equate the roughly 30% figure with business gain. First classify the call pattern: for tiny frequent functions, expose a batch C API; for long tasks, let C own a context while Go waits asynchronously. Pass length-delimited buffers, handles, and status codes, and document allocation, release, copying, and pointer lifetime.
For evidence, fix input, hardware, compiler, and linker flags; measure single-call, batched, and end-to-end benchmarks; and use CPU, memory, and blocking profiles to explain the difference. During a canary, watch tail latency, allocations, crashes, and C errors to catch lock or copying costs. Any non-cancellable C call needs isolation, a timeout, and a rollback plan.
Common mistakes
- Repeating the 30% number without the baseline, hardware, or call shape.
- Letting C retain Go pointers indefinitely to avoid a copy, violating lifetime rules.
- Adding goroutines to hide a global C lock or serial bottleneck.
- Measuring only a microbenchmark while ignoring end-to-end tails, crashes, and cleanup.
Follow-up questions and responses
When can batching make performance worse?
Batch waits increase per-request queueing and memory peaks. If data is small, latency targets are strict, or C already batches internally, the gain can disappear. Choose batch size from end-to-end p99 and throughput together.
How do you prevent a C library from leaking resources?
Wrap handles in an explicit Go lifetime object with an idempotent Close, and release on error, timeout, and cancellation. Long-running tests and native tools should confirm handles, heaps, and threads do not grow continuously.
What if Go 1.26 improves benchmarks but production regresses?
Align compiler, CGO_ENABLED, CPU features, and request mix first; then compare copying, lock waits, GC, and C-internal timings. If the regression is platform-specific, canary or roll back per platform and preserve a reproducible sample.