When is discarding a Go error with `_ =` defensible, and how do you mark it as deliberate?
answer
- the compiler lets a call statement drop everything
- make the discard explicit, not accidental
- some stdlib writes never fail
- does the next line assume success?
- the comment gives the reason, not the fact
basics
~20 sOnly when the call cannot meaningfully fail, as with a write to a bytes.Buffer, or the failure is genuinely not actionable. Write an explicit blank assignment plus a one-line comment giving the reason, so review sees a decision.
solid answer
~50 sGo lets you drop every result of a call statement without saying so: `w.Write(b)` on its own line compiles, and that silence is where dropped failures come from. Two questions decide whether dropping is defensible. First, can this call actually fail? Some standard-library writes are documented never to return a non-nil error — `bytes.Buffer.Write`, `strings.Builder.WriteString`, any `hash.Hash` — and ignoring those is correct, not sloppy. Second, if it can fail, is there anything the caller could do differently? If not, the honest move is still usually to return it upward and let someone with more context decide. When you do drop one, write `_, _ = b.WriteString(s)` with a short comment giving the reason, so the blank identifier records an intent instead of leaving the reader to guess whether you thought about it. Never drop the error from something whose success you then assume.
code
go · 5 linesvar b bytes.Buffer
// bytes.Buffer never returns a non-nil error from a write
_, _ = b.WriteString(step.Name)
_, _ = fmt.Fprintf(&b, " -> %d", step.Version)
return b.String()go deeper
Know that Go lets a call statement drop its results silently, and that the safe default is to check every error you get back rather than deciding it does not matter.
Explain the two legitimate cases — the call is documented as never failing, or the failure is truly unactionable — and show the explicit blank-identifier form with a reason comment rather than a bare call.
Demonstrate the review heuristic: does anything below assume success, could a caller act on this, is the error documented as always nil. Say why returning it upward beats dropping it in library code.
Own it as a codebase policy question: what the standard is for justifying a discard, how much of it you enforce by tooling versus review, and how you keep the exception list from growing into a habit.
## The silence Go permits Go forces you to use every variable you declare, but it does not force you to use a function's results. A call used as a statement may discard all of them: ```go fmt.Fprintf(w, "applied %d\n", version) // returns (int, error), both dropped ``` That single line is the most common way real failures disappear from a Go program. Nothing in the language distinguishes "I considered this error and it cannot matter" from "I did not notice this function returns one". The blank identifier closes that gap by making the discard visible: ```go _, _ = fmt.Fprintf(w, "applied %d\n", version) ``` To the compiler these two lines are identical. To a reader and to review they are not: the second says a person looked at the result and chose. That is the entire value of the form, and it is why the convention is worth following even though it adds characters. ## When dropping is actually correct **The call is documented as infallible.** Several standard-library writers return an `error` only because they implement `io.Writer`, and their documentation states the error is always nil: `bytes.Buffer.Write` and `WriteString`, `strings.Builder`'s write methods, and every `hash.Hash` (it never returns an error). Checking those produces dead branches that no test can cover, so the honest code drops them. This is the one category where `_ =` needs little defending. **Failure is genuinely unactionable and already visible.** A last-ditch write to standard error while the process is already unwinding, or a best-effort metric emission on a path where degrading is the intended behaviour, can qualify. The test is not "I do not want to handle it"; it is "there is no different action any caller could take, and the outcome is not something anyone later assumes succeeded". **The result is genuinely optional and the failure carries no information.** Rare, and worth being suspicious of. ## When it is not correct The disqualifying question: does any later line assume this call succeeded? If yes, dropping the error converts a clear failure into a corrupted state that surfaces somewhere else entirely. A migration tool that ignores the error from writing the new schema version and then reports the run as successful is not saving a line of code; it is choosing where the incident will be discovered. The second disqualifier is scope. Deciding that a failure does not matter is a decision about the *caller's* world, and library code does not have that information. Inside a package other teams import, the default is to return the error and let the application decide, even when you are fairly sure nobody will care. The third is the quiet one: `_ =` on something that also has a *side effect* you rely on. Ignoring the error from a call that was supposed to establish a precondition is the same bug as not calling it at all, just harder to see. ## Writing it so it survives review The shape that works: ```go var b bytes.Buffer // bytes.Buffer.Write never returns a non-nil error _, _ = b.WriteString(step.Name) _, _ = fmt.Fprintf(&b, " -> %d", step.Version) ``` Three properties matter. The blank identifier is explicit, so the discard is syntactically visible. The comment states the *reason*, not the fact — "ignore error" adds nothing a reader could not see; "never returns a non-nil error" is a claim a reviewer can check. And the reason is specific to this call, which means copying the pattern to a different call forces someone to re-justify it. A reason that reads "we cannot do anything about it here" deserves a follow-up question in review: often the answer is that the function should return the error rather than swallow it, and the discard is really a design smell one level up. ## The reviewer's heuristic When you find an ignored error in a diff, ask in order: does this call return an error at all (some readers assume it does not), is the error documented as always nil, does anything below assume success, and could this function's caller do something the author could not? Two of those four are answerable from the documentation alone, which makes this one of the cheaper things to check carefully. And when the answer is that it should have been handled, the fix is nearly always to return it, not to log it and continue — a call that fails and continues has told nobody anything the calling code can act on.
- Is `_ = f()` different from writing `f()` as a bare statement?Not to the compiler — a call statement may discard all its results either way. The difference is entirely for humans: the blank identifier records that someone saw the error result and chose to drop it, while the bare call is indistinguishable from not having noticed.
- Which standard-library calls are safe to ignore on those grounds?Ones whose documentation says the error is always nil: writes to a bytes.Buffer or a strings.Builder, and writes to any hash.Hash. They return an error only to satisfy io.Writer. Checking them creates a branch no test can exercise, so an explicit discard with a comment is the honest form.
- A colleague argues a failure here is unactionable, so dropping it is fine. How do you probe that?Ask whether any line below assumes the call succeeded, and whether the caller — possibly in another team's service — might act differently than this function can. If either answer is yes, the error should be returned rather than discarded. Library code in particular rarely has enough context to declare a failure irrelevant.
saying these in an interview costs you the question
- Drops an error to silence a linter and moves on
- Ignores a failure whose success later lines assume
- Says library code may decide a failure is unimportant
- Writes ignore error as the justifying comment
- Thinks checking an always-nil error is required rigour