What are the tradeoffs of using cgo? When would you avoid it?
Question 426HardGo 1.22 to 1.25
"cgo is not Go." The costs:
- Call overhead: each Go-to-C call switches to the system stack and tells the scheduler. That is tens of nanoseconds, versus about 1 ns for a Go call. Chatty per-element calls kill performance, so batch the work.
- Threads: a goroutine blocked in C holds an OS thread. Thousands of blocking C calls mean thousands of threads.
- Builds: you need a C toolchain, cross-compiling becomes painful, builds get slower, and you lose the easy fully static binary. With cgo on,
net(DNS) andos/useruse libc and the binary links dynamically. WithCGO_ENABLED=0those packages fall back to pure-Go implementations. - Safety: C memory is invisible to the GC and the race detector, a crash in C takes down the process, and the pointer-passing rules are strict.
- Tooling: pprof cannot see C stacks, and debugging is harder.
package main
/*
#include <stdlib.h>
#include <string.h>
static size_t clen(const char* s) { return strlen(s); }
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
cs := C.CString("hello") // malloc'd copy: must free
defer C.free(unsafe.Pointer(cs))
fmt.Println(int(C.clen(cs))) // 5
}
When to use it: mature C libraries with no Go equivalent (SQLite, libvips, GPU drivers). Alternatives: pure-Go ports (modernc.org/sqlite), a subprocess or RPC sidecar, purego-style dynamic loading, or WASM. Interviewers want you to name the overhead, static-build and cross-compilation costs, and batching.
More on Modules, Packages & Tooling
- Q424How would you separate integration tests from unit tests using build tags? What alternatives exist?
- Q425How does go generate work, and what are best practices around it?
- Q427What are the cgo pointer-passing rules, and how does runtime.Pinner help?
- Q428How do you cross-compile Go and produce a fully static binary for a scratch container?
- Q429How do you inject a version string at build time with -ldflags? What does this program print?
- Q430What build metadata does a Go binary carry, and how do you make builds reproducible?