Create A Custom Error Type In Go
Fast recall
Quick revision
Core answer
Whether you understand Custom Error Types Basics beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The error interface is one method. A struct that adds it becomes an error, and its fields travel with the failure:
type NotFoundError struct {
Name string
}
func (e *NotFoundError) Error() string {
return "not found: " + e.Name
}
// returning it
func lookup(name string) error {
return &NotFoundError{Name: name}
}The caller's side is errors.As — the type-aware chain walk:
err := lookup("cfg")
var nfe *NotFoundError
if errors.As(err, &nfe) {
fmt.Println("which one:", nfe.Name) // the field the caller needed
}That's the whole pattern: struct for the data, Error() for the message, pointer receiver for the method, errors.As at the consumer. The value over a plain errors.New string is exactly the fields — 'which record failed' stops being prose in a message and becomes data the caller branches on.
Create A Custom Error Type In Go
Direct Answer
Any type implementing error: a struct plus an Error() string method. Callers extract it with errors.As.
Deep Dive & Architecture
The moving parts are four, and each has a job:
The struct carries the DATA the caller will branch on — fields are the whole point. What fields? Ask what the caller's next line will be: RetryAfter for a rate limiter, Name for a lookup miss, Status for an HTTP layer. The struct is the caller's API into the failure.
The Error() method makes it an error — Go's structural typing means no registration, no inheritance; implement the one method and you're in the club. The message should render the fields (so logs are useful even where the caller doesn't use errors.As), but the fields are for code and the message is for logs — different audiences.
The pointer receiver serves two mechanics: identity (comparing *NotFoundError values — the As target matches by pointer) and nil-ness (a *NotFoundError variable starts nil). The convention: pointer, always — the ecosystem's As targets are written pointer-style, and consistency keeps the As patterns uniform.
The constructor is ordinary but deserves the standard shape:
func NewNotFound(name string) error {
return &NotFoundError{Name: name}
}
Returning error (not *NotFoundError) is the standard API choice: the caller sees the interface, uses errors.As when they need the fields, and your function signature stays the honest 'I can fail with various things.'
The pattern's economy: one struct, one method, one constructor — and the failure carries machine-readable data instead of prose. When a codebase's errors need MORE (codes, HTTP statuses, retryability), the extensions are methods on the same struct — the design extends without a rewrite.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Custom Error Types Basics in a real project?
- How would you compare Custom Error Types Basics with an alternative?
- What is a common mistake when using Custom Error Types Basics?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.