Explain the performance overhead of a cgo call

Tests cgo transition penalties and scheduler semantics. A strong answer cites the ~100x overhead (~171ns vs ~1.8ns), notes C blocks an OS thread and starves the scheduler, and warns about memory copy taxes. Red flag: claiming cgo is free or ignoring blocking.
WHAT THIS TESTS: Whether you understand that cgo is not a zero-cost foreign function interface. The interviewer wants to hear that you have internalized the quantitative call overhead, the scheduler implications of dropping out of Go runtime control, and the memory ownership friction at the C boundary. Senior candidates should connect these costs to system design decisions like batching, moving loops into C, or using unsafe zero-copy contracts when appropriate.
A GOOD ANSWER COVERS: First, quantify the baseline call overhead. The canonical microbenchmark shows a minimal cgo call at roughly 171 nanoseconds per operation versus a Go function call at roughly 1.83 nanoseconds, which is about two orders of magnitude slower. Second, explain scheduler impact: once execution enters C, it runs on an operating system thread and the Go scheduler cannot multiplex other goroutines onto that thread until the C call returns, so blocking C work effectively removes a thread from the Go runtime pool. Third, describe the memory boundary tax. Because C is not garbage collected, passing data across the boundary usually requires copying through helpers like C.CString or C.GoBytes, which increases CPU and memory pressure. Fourth, mention practical mitigations: batch operations to amortize the per-call cost, push iteration logic down into C, or carefully use unsafe.Pointer slices when you can guarantee the underlying C memory lifetime.
COMMON WRONG ANSWERS: Treating cgo overhead as negligible without quoting approximate numbers or acknowledging the ~100x gap. Claiming that C calls are non-blocking from the scheduler perspective, or that the Go runtime can park the goroutine and reuse the thread while C is executing. Suggesting unsafe.Pointer to avoid every copy without explaining the lifetime contract that keeps the C memory valid. Proposing to spawn a goroutine per cgo call without realizing that each blocked C call pins an OS thread and can exhaust the runtime thread limit.
LIKELY FOLLOW-UPS: How would you redesign a hot path that makes tens of thousands of cgo calls per second? When is it safe to return a Go slice backed by C memory without copying? How does a long-running C call affect GOMAXPROCS and tail latency? What happened in the RocksDB iterator case where C.GoBytes copied keys unnecessarily?
ONE CONCRETE EXAMPLE: In the RocksDB wrapper, iterating keys originally used C.GoBytes on every call to Key, copying data even when the caller only needed a prefix check. The team introduced an unsafeKey method that constructed a slice header pointing directly into the iterator's C memory, eliminating copies. This was safe only because the RocksDB iterator guaranteed the key pointer remained valid until the next iterator operation, but it required a strict internal contract that would be dangerous to expose in a public API.
Read the original → cockroachlabs.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.