Efine Sentinel Errors In Go
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
A sentinel error is an error value created once and exported so callers can test for it. The shape is always the same:
// package store
var ErrNotFound = errors.New("record not found")
func Get(id string) (*Item, error) {
// ...
return nil, ErrNotFound // the same value, every time
}And the caller side:
item, err := store.Get(id)
if errors.Is(err, store.ErrNotFound) {
// distinguishable from other failures
}The naming convention is deliberate: Err-prefixed, so it reads as a condition at the use site. Because the package creates the value once and returns that same value on every failure, callers can reliably identify the case — and because errors.Is is used rather than ==, the match still works if an intermediate layer wrapped the error with %w while adding context.
Sentinels suit a small, stable set of conditions callers branch on: not-found, already-exists, permission-denied. When the set grows or the errors need to carry data (which record? what status?), custom error types take over — but the sentinel is the right first tool and the standard library is full of them (io.EOF, context.DeadlineExceeded, sql.ErrNoRows).
Efine Sentinel Errors In Go
Direct Answer
Package-level var: var ErrNotFound = errors.New("not found"). Callers match with errors.Is(err, ErrNotFound). The single value is the identity.
Deep Dive & Architecture
What makes the sentinel reliable is a chain of small invariants:
- errors.New returns a pointer — two calls are never equal (identity semantics).
- The var is created once at package init — every return of ErrNotFound shares that one pointer.
- errors.Is walks the Unwrap chain — wrapping the sentinel with %w preserves the match.
- The Err prefix signals 'this is a value to compare against' at the call site.
Break any link and the pattern fails: create the error inside the function (new pointer each time — callers can never match), return a wrapped copy without %w (Is can't see through), or name it without the convention (callers miss it).
The alternative that eventually outgrows sentinels: typed errors with fields. sql.ErrNoRows tells you WHICH row was missing only by context; a *NotFoundError{Name: "users/42"} can carry it. The standard library uses sentinels where the condition is universal and types where details matter — a good calibration for your own APIs. Start with the sentinel; add a type when the caller needs more than 'this happened'.
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?
- What is a common mistake when using Errors New?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.