Can You Wrap Multiple Errors With Fmt.errorf
Fast recall
Quick revision
Core answer
Practical understanding of Fmt Errorf.
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
fmt.Errorf's %w takes one cause — the format can contain it once, and the wrap tree holds one edge. To wrap several failures, Go 1.20 added errors.Join:
var errs []error
for _, task := range tasks {
if err := task.Run(); err != nil {
errs = append(errs, fmt.Errorf("task %s: %w", task.Name, err))
}
}
return errors.Join(errs...) // one error, every failureThe joined error prints all components' messages (newline-joined), and errors.Is/As walk every component — any cause anywhere in the batch is findable:
errors.Is(joined, someComponentErr) is true for each. errors.As extracts any component's type.
The pattern it serves: parallel validation or batch execution where returning the first error throws away information. 'Form had 3 invalid fields' beats 'form had 1 invalid field' three round-trips in a row. Sequential code still returns the first error — the later steps didn't run, so reporting them would be fiction.
Can You Wrap Multiple Errors With Fmt.errorf
Direct Answer
Not with %w — exactly one. Go 1.20's errors.Join(err1, err2) wraps several into one error that Is-matches each component.
Deep Dive & Architecture
errors.Join is the 1.20 answer to a pattern the community had already reinvented several times (hashicorp/go-multierror, the multierr libraries) — and the standard library's version has one API decision those didn't: it's an error, not an errors-collection API. You return it like any error; callers who don't care see one error with a newline-joined message.
The semantics that make Join correct (and hand-rolled versions subtly wrong):
-
Is: joined matches a target if ANY component matches — callers still find sentinels inside batches.
-
As: extracts the first component of the requested type — the caller gets typed data out of a many-failure return.
-
Unwrap() []error: the Go 1.20 multi-edge — Is/As treat a []error unwrap as a branch, walking every branch. The subtle part older versions got wrong: walking a tree (Join of Joins) without double-visiting or missing branches. The stdlib's tree-walk is the tested one.
-
nil handling: Join drops nil components, and Join() of all-nils returns nil — the builder pattern (collect errs, Join at the end) needs no guard.
When NOT to use it: sequential failure (step 2 didn't run because step 1 failed) — return the first error; joining implies 'all of these were attempted and failed,' which would be fiction. Join is for genuine parallelism: batch validation, fan-out requests, per-field form errors. The message shape tells the story: a Join prints as a list; a sequential chain prints as a narrative.
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 Fmt Errorf, and when would you use it?
- When would you use Fmt Errorf in a real project?
- How would you compare Fmt Errorf with an alternative?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.