skip to content

What do the -s and -w linker flags do in go build -ldflags, and what do they cost?

level: middleimportance: nice to knowfreq 40%

answer

  1. two letters, a smaller artefact
  2. one drops DWARF, one drops the symbol table
  3. panic traces still name functions
  4. the debugger is what you give up

basics

~20 s

The -w flag tells the Go linker to omit the DWARF debug information and -s to omit the symbol table as well, which cuts binary size substantially. The cost is symbolic debugging: a debugger can no longer map addresses back to source.

solid answer

~50 s

Both are linker flags passed through `-ldflags`, and they are conventionally passed together as `-ldflags "-s -w"`. `-w` omits the DWARF debug tables; `-s` omits the symbol table and debug information. The payoff is size — typically something like a quarter off a release binary, which matters when the artefact is pulled as a container image layer many times a day. The cost is that a debugger has nothing to work with: no source-line mapping, no variable names, no stepping. What you keep is important too. The Go runtime carries its own function and line metadata so it can unwind, and the stripping flags do not remove it, so panic stack traces still print readable function names and line numbers in production. The embedded build information is data rather than symbols, so `go version -m` still identifies a stripped binary.

code

text · 2 lines
text
$ go build -o daemon .
$ go build -ldflags "-s -w -X main.version=1.4.0" -o daemon-release .

go deeper

for a junior

Recall that -s and -w only shrink the artefact and change nothing about how the program runs, and that they go inside the same -ldflags argument as any version stamping.

for a middle

Explain which tables each flag removes and which metadata the Go runtime keeps for its own tracebacks. The distinction between DWARF and the runtime's function tables is what the question is really testing.

for a senior

Weigh the saving against how you actually debug production, and say what you keep — an unstripped artefact, a recorded revision — so that a stripped binary never becomes an investigation you cannot run.

for a principal

Set the default for the estate: whether release images ship stripped, whether unstripped build outputs are retained and for how long, and on what grounds a service is allowed to deviate.

## What the two flags are Both belong to the Go linker and reach it through `go build -ldflags`: - **`-w`** — omit the DWARF symbol table. DWARF is the standard debug format: it maps machine addresses to source files and lines, describes local variables and their types, and describes stack frame layout. Debuggers and several native profiling tools read it. - **`-s`** — omit the symbol table and debug information. This removes the classic symbol table that external tools use to name addresses. In practice they are passed together, in the same `-ldflags` argument as any version stamping: `go build -ldflags "-s -w -X main.version=1.4.0"`. ## What you gain Size, and nothing else. Debug tables are a large fraction of a Go binary — a typical release build shrinks noticeably, often around a quarter. Nothing about run-time speed, memory use or start-up changes: none of those tables are loaded at run time by the program itself. If someone claims stripping makes the program faster, that is a misconception worth correcting. Where size does matter is distribution. A daemon shipped as a container image layer is pulled by every node on every rollout; a self-updating agent downloads itself. Those are the contexts where teams routinely strip. ## What you lose Symbolic debugging. Attaching a debugger to a stripped binary gives you addresses, not source. You cannot set a breakpoint on a line, inspect a named local, or step through a function meaningfully. Native profiling and tracing tools that symbolise Go frames through DWARF lose the same thing. So the question is not "is stripping good" but "is live debugging part of how we operate this service". If the answer is yes — you attach to production processes, or your host-level tooling reads DWARF — stripping is a poor trade. If the answer is no, and it usually is for a service that is diagnosed through logs, metrics and profiles, then the size saving is nearly free. ## What survives, and why it matters This is the part people get wrong, and it is what makes stripping tolerable. **Panic tracebacks still work.** The Go runtime does not use DWARF to unwind. It carries its own function metadata — the tables the runtime needs to walk the stack and name frames — and that data is required by the program itself, so the linker keeps it. A panic in a stripped production binary still prints a stack trace with function names and line numbers. This is the single most reassuring fact about `-s -w`: the diagnostic you rely on most in production is untouched. **Build information survives.** The module path, the dependency list and the recorded build settings are embedded as ordinary data in the binary, not as symbols, so `go version -m` still identifies a stripped artefact and `runtime/debug.ReadBuildInfo` still works from inside the process. Stripping affects debuggability, not identity. **Injected version strings survive.** A `-X` stamp writes into a package-level variable's data. Stripping removes debug tables, not program data, so the two flags coexist happily in one `-ldflags` string. ## What stripping is not It is not a security control. Removing debug tables raises the effort of casual reverse engineering by a little, and that is all: the code, the strings and the embedded build metadata are all still there. Treating `-s -w` as protection for anything is a mistake. It is also not related to static versus dynamic linking, or to whether cgo is enabled. Those are separate decisions with their own flags. ## A reasonable posture Strip release artefacts if size is a real cost for you, do not strip local development builds, and make sure that whatever identity you rely on — the injected version string, the recorded revision — is present and readable on the stripped artefact, so that losing the debugger does not also mean losing the ability to work out what you are looking at.

  • Do Go panic stack traces still show function names after -s -w?
    Yes. The runtime finds function names and line numbers in its own metadata, which the linker keeps because the program needs it to unwind the stack. The stripping flags remove the DWARF tables and the classic symbol table that external tools read, not the runtime's own tables, so a panic in production still prints a readable trace.
  • Can you still tell what a stripped Go binary was built from?
    Yes. Build information is embedded as data rather than symbols, so `go version -m` on the stripped artefact still prints the module path, the dependency list and the recorded build settings, and `runtime/debug.ReadBuildInfo` still works from inside the process. Stripping costs you debugging, not identity.
  • When is -s -w the wrong trade?
    When attaching a debugger to a running production process is part of how you operate the service, or when host-level profiling and tracing tools symbolise Go frames through DWARF. If either is true, either keep the debug information or retain an unstripped copy of the same build so an investigation has something to attach to.

saying these in an interview costs you the question

  • Claims -s -w makes the program run faster
  • Says stripping removes the embedded build information
  • Thinks panic traces become unreadable after stripping
  • Treats stripping as protection against reverse engineering
  • Applies -s -w to local development builds by habit