A common mistake is to add an error message without deciding whether callers still need the underlying error. Wrapping adds context or marks an error while keeping the original available for inspection. In Go 1.13 and later, fmt.Errorf with %w wraps; %v includes the message but does not preserve the error for unwrapping. section 7.2
Return context instead of logging twice
If coordinate validation fails, logging inside both the validator and GetRoute produces two logs for one failure. Returning the error without context avoids duplicate handling but loses whether source or target coordinates failed. Wrap at the call site instead: if err := validateCoordinates(srcLat, srcLng); err != nil { return Route{}, fmt.Errorf("failed to validate source coordinates: %w", err) } The caller can log once and still receive both the location of the failure and the validation error. section 7.5
Check through the wrapper
A direct type switch can recognize a transientError returned by a database call, then stop recognizing it when an intermediate function returns fmt.Errorf("failed to get transaction %s: %w", id, err). The outer error is no longer directly a transientError. Use errors.As(err, &transientError{}) to search the chain so an HTTP handler can still choose 503 rather than its default 400. section 7.3
Choose how much of the error to expose:
- Return err directly when there is no useful context or marking to add. section 7.2
- Use %w when callers should inspect the underlying error; use a custom error type when the wrapper itself must mark a specific kind of failure. section 7.2
- Use %v when adding a message but deliberately not exposing the underlying error. Exposure can couple callers to that error; check wrapped types with errors.As and wrapped values with errors.Is. section 7.2 section 7.8


