Nside Of Errors.new For Caller-side Decisions
Fast recall
Quick revision
Core answer
Practical understanding of Errors New.
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
errors.New gives callers exactly one bit of information: which sentinel it is. If a caller needs to know more — which key wasn't found, what the retry-after is, whether the failure is retryable — a bare string error can't say. The caller can't parse the message, because messages are for humans and change.
The progression looks like this:
// 1. sentinel — matchable, no data
var ErrRateLimited = errors.New("rate limited")
// 2. typed — matchable AND data-carrying
type RateLimitError struct {
RetryAfter time.Duration
}
func (e *RateLimitError) Error() string {
return fmt.Sprintf("rate limited, retry after %v", e.RetryAfter)
}
// caller
type target *RateLimitError
if errors.As(err, &target) {
time.Sleep(target.RetryAfter) // the data the caller needed
retry()
}errors.As finds the type anywhere in the wrap chain and gives the caller the struct — with fields, not prose. The sentinel answered 'was it rate limiting?'; the type also answers 'for how long?'.
So the honest summary: errors.New is perfect when the identity is the whole story. The moment the caller's next action depends on data about the failure — and retry-backoff, redirect targets, and which-record questions all do — the error needs to be a type. Not a string that the caller regexes; a value with fields.
Nside Of Errors.new For Caller-side Decisions
Direct Answer
A static string carries no data — callers can only match identity, not extract fields. Custom error types carry the details.
Deep Dive & Architecture
Why is 'parse the message' so strongly forbidden? Because it couples the caller to your prose. The day the message becomes 'rate-limited, try in 30s' for tone, every string-matching caller silently breaks — no compile error, just a failed match at runtime that looks like a different error. Struct fields are a contract; strings are a convenience.
The deeper structure here: an error value's job is to let the caller branch correctly. Different branches need different information:
- 'Is this retryable?' → a Retryable() bool method, or a sentinel set membership
- 'What do I tell the user?' → a UserMessage() or typed codes
- 'What do I log/alert?' → the wrapped chain already carries it
- 'Which record failed?' → a field on the type
errors.New answers none of these beyond identity; fmt.Errorf with %w preserves the chain that does; a custom type with methods carries the answers directly. The standard library's os.PathError is the canonical middle step: it's a struct with Op, Path, and Err fields — a type, because 'which path failed' is the first thing every caller of file code asks. When your callers would ask a question the message can't answer structurally, you've outgrown errors.New.
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 Errors New, and when would you use it?
- When would you use Errors New in a real project?
- How would you compare Errors New with an alternative?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.