What does building with CGO_ENABLED=0 change about a Go binary, and what do you give up?
answer
- a build-time switch, not a runtime one
- no C compiler in the picture
- two stdlib packages ship two implementations
- one file, no dynamic loader
- hostname and user lookups change owner
basics
~20 sCGO_ENABLED=0 disables cgo, so no C code is compiled in and the standard library uses its pure-Go net and os/user code. The result is a self-contained static binary that needs no C library on the host.
solid answer
~40 s`CGO_ENABLED` is an environment variable that `go build` reads. Setting it to `0` turns cgo off: any file with `import "C"` is excluded by build constraints, and two standard-library packages that ship two implementations each — `net` for hostname lookups and `os/user` for user lookups — fall back to their pure-Go versions. On Linux the practical effect is that the Go linker no longer hands off to the system C toolchain, so you get a statically linked executable with no dynamic loader and no libc dependency. What you give up is real: the pure-Go resolver only understands `files` and `dns` sources, so hostnames or users served by other name-service modules (mDNS, an LDAP-backed directory) stop resolving, and any dependency that binds a C library will not build at all.
code
text · 8 lines$ CGO_ENABLED=0 go build -o app .
$ file ./app
./app: ELF 64-bit LSB executable, x86-64, ... statically linked, ...
$ CGO_ENABLED=1 go build -o app .
$ file ./app
./app: ELF 64-bit LSB executable, x86-64, ... dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2, ...go deeper
Be ready to say what the switch does in one breath: no C code, pure-Go net and os/user, one static file with no libc dependency. Knowing it is set at build time, not run time, is the part that gets checked.
Explain the mechanics: cgo files are excluded by build constraints, the Go linker links internally instead of handing off to the C toolchain, and the resulting ELF names no interpreter. Be able to show the difference with file or ldd.
Show that you know the switch changes behaviour, not just packaging. Name what the pure-Go resolver cannot do and how you would verify a service does not depend on it before flipping the flag in a release pipeline.
Own it as a release-artifact policy: what the default is, who is allowed an exception, and what evidence is required to grant one. The tradeoff is portability of the artifact against fidelity to the host's name-service configuration.
## What cgo is, in one paragraph cgo is the mechanism that lets Go code call C code. When a Go file contains `import "C"`, the `go` command runs cgo over it, invokes a C compiler, and links the result into the program. Even if *your* code never imports `C`, cgo matters to you, because parts of the Go standard library are written twice — once in Go and once against the host's C library — and cgo decides which copy you get. ## What the variable actually is `CGO_ENABLED` is an environment variable consulted by `go build`, `go test` and friends. For a native build it defaults to `1` when a usable C toolchain (gcc or clang) is on `PATH`. Setting `CGO_ENABLED=0` on the command line — `CGO_ENABLED=0 go build -o app .` — turns the whole facility off for that build. It is a *build-time* switch. Nothing about it can be changed after the binary exists; you cannot flip it at startup. ## Three consequences **1. Files that import C disappear.** cgo files carry an implicit build constraint, so with cgo off they are simply not part of the package. If a package has no non-cgo implementation of the same symbols, the build fails outright. That is the honest answer to "why won't this dependency compile in our release build?" **2. Two standard-library packages switch implementation.** `net` has a hostname resolver written in Go and a path that calls the C library's `getaddrinfo`; with cgo disabled, only the Go one exists in the binary. `os/user` likewise has a pure-Go implementation that parses `/etc/passwd` and `/etc/group` directly, versus a cgo path that calls the C library's password-database functions. These are the two packages people actually notice. **3. Linking changes.** When cgo is in play, the Go linker uses *external* linking: it hands the object files to the system C toolchain, and the result is an ELF binary that is dynamically linked and names an interpreter — the dynamic loader, typically `/lib64/ld-linux-x86-64.so.2` on 64-bit x86 Linux. With `CGO_ENABLED=0` the Go linker links internally and emits a statically linked executable with no interpreter and no shared-library dependencies. This is why teams that ship a single file into an image containing nothing else standardise on `CGO_ENABLED=0`: the binary plus a kernel is the entire runtime requirement. ## How you check Inspect the artifact, do not assume: - `file ./app` reports either `statically linked` or `dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2`. - `ldd ./app` prints the shared libraries needed, or says the file is not a dynamic executable. One `file` call in the release pipeline catches an accidental cgo dependency long before a host does. ## What you give up, concretely The cgo-backed resolver goes through the host's C library, which means it honours the host's full name-service configuration. Go's pure-Go resolver reads `/etc/resolv.conf` and understands the `files` and `dns` sources; it cannot load the C library's other name-service modules, because those are shared objects opened at run time. So a hostname that only resolves through mDNS, or a username that only exists in a directory service, resolves on a developer laptop and fails inside a `CGO_ENABLED=0` binary. Nothing is logged about the switch — the lookup just does not find the name. The same applies to `os/user`: a user that exists only in a directory is invisible to code that parses `/etc/passwd`. You also give up the ability to use any dependency that wraps a C library. That is a build failure rather than a silent behaviour change, which is the kinder of the two. ## Platform note This whole discussion is a Linux one. On macOS, Apple does not support statically linking the system library, so "a fully static binary" is not a goal you can reach there; on Windows the packaging story is different again. When an interviewer asks about `CGO_ENABLED=0`, they almost always mean a Linux server artifact. ## The short version to say out loud Off means: no C, pure-Go `net` and `os/user`, one static file. The cost is that name and user lookups now follow Go's rules rather than the host's, and C-backed dependencies stop building.
- Which standard-library packages actually behave differently when cgo is disabled?`net` and `os/user` are the two that matter. `net` loses the path that calls the C library's `getaddrinfo` and uses its pure-Go hostname resolver instead; `os/user` stops calling the C password-database functions and parses `/etc/passwd` and `/etc/group` itself. Everything else in the standard library is Go either way.
- If a dependency requires cgo, what does the failure look like?A build failure, not a runtime one. With cgo off, files containing `import "C"` are excluded by build constraints, so the package is left with missing symbols or no implementation and `go build` reports undefined references or an empty package. That is preferable to a silent behaviour change — you find out in CI.
- How do you prove in CI that a released binary is really static?Run `file` or `ldd` on the built artifact as a pipeline step and fail the release if it reports a dynamic executable or lists shared libraries. Asserting the environment variable was set is weaker: someone can add a dependency that forces external linking without touching the build script.
A cgo-enabled binary is like a recipe that says "use the kitchen's stock": it depends on what the host has installed. CGO_ENABLED=0 packs its own ingredients — heavier to assemble, but it cooks anywhere.
saying these in an interview costs you the question
- Thinks CGO_ENABLED can be changed at run time
- Believes a CGO_ENABLED=0 binary embeds a copy of glibc
- Assumes disabling cgo changes nothing about hostname resolution
- Claims the Go toolchain ships its own C compiler
- Says a static binary must be rebuilt for each kernel version