Blank Identifier's Role With Errors
Fast recall
Quick revision
Core answer
Practical understanding of Multiple Return Error.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
The blank identifier is how you opt out of one side of the (value, error) pair explicitly. _, err := f() says 'I need only the error' — common when you only care whether something succeeded. v, _ := f() says 'I need only the value, ignore the error' — and that second form deserves scrutiny.
Ignoring an error is sometimes correct: a best-effort cache write, a metrics flush, a debug log where failure changes nothing. In those cases _ documents the decision at the call site — a reader sees that the error was considered and deliberately dropped, not overlooked.
But most v, _ := calls are bugs in waiting. The failure the error reported doesn't disappear; it moves downstream — the zero value gets used, and the real cause becomes a mystery later ('why is this config empty?', 'why did nothing get written?'). The compiler accepted it silently, no test caught it, and production is where it surfaces. That's why linters flag discarded errors by default: the pattern is legal but the justification should be local and visible.
The middle path: don't ignore, log. v, err := f(); if err != nil { log.Printf("...: %v", err) } — three lines that convert a silent failure into a diagnosable one. Use _ only where you can say, out loud, why the error doesn't matter.
_, err := os.Stat(path) // value unused; error kept — fine
v, _ := risky() // error DISCARDED — the lint smell
v, err := risky(); if err != nil { log.Printf("%v", err) } // honestBlank Identifier's Role With Errors
Direct Answer
_, err := f() takes only the error; v, _ := f() ignores the error. The second is a lint smell — you lose the failure signal silently.
Deep Dive & Architecture
The mechanism is worth one layer down: the blank identifier is a write-only pseudo-variable. You can assign to it as many times as you like, read from it never. For the (value, error) contract, it's the explicit release valve — Go requires you to handle unused returns, and _ is how you say 'unused on purpose.'
The interesting asymmetry: _, err := is almost always fine (you want the error; the value being zero on failure is irrelevant), while v, _ := is the one that hides bugs. The direction matters because the error is the signal and the value is the payload — discarding the payload is harmless when the signal says the payload is absent; discarding the signal means the payload's absence will surprise you.
The enforcement history explains the ecosystem's stance: Go's compiler checks unused VARIABLES but not unused return values in general — the single-return-value assignment would be an error, but multi-return with _ never is. That gap between 'the language lets me' and 'the ecosystem expects' is exactly what errcheck-style linters filled. When a language leaves a discipline to convention, the convention grows teeth as tooling.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is Multiple Return Error, and when would you use it?
- When would you use Multiple Return Error in a real project?
- What is a common mistake when using Multiple Return Error?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.