What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
In Go, choose error handling based on what callers should be able to learn: use %w when the underlying error is part of your API, %v when you want context without exposing that error for inspection, errors.Is to match a condition, and errors.As to retrieve a structured error. For independent failures, Go 1.20 and later also provide errors.Join.
What wrapping promises to callers
An error is an interface value. A wrapper adds context while exposing an underlying error through an Unwrap() error method. The standard inspection functions can follow that relationship, so callers need not assume the returned error is the original value or appears at a particular wrapper depth.
As an Amazon Associate I earn from qualifying purchases.
For example, when loading a named configuration file:
if err != nil {
return fmt.Errorf("load config %q: %w", name, err)
}
The added text helps identify the failed operation. The %w also makes the underlying error inspectable. As Go authors Damien Neil and Jonathan Amsterdam put it, “Wrapping an error makes that error part of your API.” (Go Blog, “Working with Errors in Go 1.13”)
#1 Best Overall
That is why wrapping is an API design choice, not just a formatting choice. If callers are meant to rely on a condition or type, document it and preserve that promise consistently. If the cause is an implementation detail, exposing it can constrain future changes.
When to expose a cause—and when to hide it
Expose errors callers supplied
If a function accepts an io.Reader, a read failure comes from a dependency supplied by the caller. Letting the caller inspect that failure may be useful. Wrapping it with %w adds operation context while retaining the caller’s ability to identify the underlying error.
Keep internal implementation details private
A package that uses a database internally may not want callers to depend on a database-specific error such as sql.ErrNoRows. If the package wraps and exposes that sentinel, callers may build behavior around it. Replacing the database later could then break those callers even if the package’s intended abstraction has not changed.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →When the underlying cause should remain private, %v can include its rendered message without making it available through unwrapping:
return fmt.Errorf("load config %q: %v", name, err)
The visible text can resemble the %w version, but the inspection contract differs. You can also translate a dependency failure into an error that represents your package’s own stable contract.
How should I change my error-handling code to work with the new features?
Keep ordinary nil checks as they are: if err != nil still checks whether a call returned an error. When matching an error that may be wrapped, use errors.Is rather than equality. The Go FAQ recommends this change for wrapped errors. (Go error values FAQ)
Rank #3
Use a sentinel for a stable condition
A sentinel is a package-level error value callers can recognize, such as a documented “not found” condition. The package can add context while wrapping it:
return fmt.Errorf("load config %q: %w", name, ErrNotFound)
Callers should test the promised condition with errors.Is:
if errors.Is(err, ErrNotFound) {
// Handle the documented condition.
}
errors.Is checks whether the error or an error it wraps matches the target. This avoids relying on direct equality with the returned wrapper.
Rank #4
Use a typed error for structured details
When callers need data such as a path, query, or field, a structured error type can communicate that information. Use errors.As to find a matching type through wrappers rather than asserting the type of the top-level returned value:
var pathErr *PathError
if errors.As(err, &pathErr) {
fmt.Println(pathErr.Path)
}
Document which types callers may rely on. Undocumented concrete error values can become accidental API commitments just as easily as exposed dependency sentinels.
How to choose the error behavior
| Caller need | Approach | Contract effect |
|---|---|---|
| Add context and let callers inspect the cause | Wrap with %w |
The underlying condition or type is part of the API; document what callers may inspect. |
| Add context but keep the cause private | Format with %v or translate the failure |
Callers receive context without an unwrap path to that dependency error. |
| Recognize a stable condition | Use a sentinel and match with errors.Is |
The documented condition remains recognizable through wrappers. |
| Read structured error details | Use an error type and find it with errors.As |
The documented type and its relevant fields become part of the caller-facing contract. |
| Report independent failures together | Use errors.Join when Go 1.20 or later is supported |
The returned error represents multiple underlying errors, not one linear cause. |
When multiple failures should be joined
Go 1.20 expanded error wrapping to support multiple underlying errors. A custom error can implement Unwrap() []error; fmt.Errorf can use multiple %w verbs; and errors.Join combines supplied non-nil errors into an error that wraps them. errors.Is and errors.As inspect this multi-error structure. (See the Go 1.20 release notes and errors package documentation.)
Best Value
Join errors when a single operation has independent failures worth reporting together. A joined error is a tree of underlying errors, not a chain with one cause at each step. Callers can test for a matching condition or type in that tree, so the contract should explain what matches are meaningful.
Design the contract before choosing the format verb
- Decide whether the underlying cause belongs to the caller-facing abstraction or is an internal implementation detail.
- For stable conditions, document the sentinel and expect callers to use
errors.Is. - For structured information, document the error type and relevant fields callers can retrieve with
errors.As. - Use
%wonly when exposing the underlying error is intentional; otherwise use%vor translate the failure. - Use
errors.Joinfor independent failures, and describe the multi-error matching behavior callers can rely on.
Go’s 2025 discussion of error-handling syntax notes a longstanding complaint about verbosity: “One of the oldest and most persistent complaints about Go concerns the verbosity of error handling.” — Robert Griesemer, Go Blog. That complaint is about the language’s error-handling syntax; it is not a measured finding about wrapping or error-handling outcomes.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →




