This is a searchable reference for Go’s standard error patterns — the canonical sentinel errors exported by the standard library (io.EOF, sql.ErrNoRows, context.Canceled), the concrete error types you inspect with errors.As (*os.PathError, *net.DNSError, *json.SyntaxError), and the errors/fmt helpers that tie them together.
How it works
Go errors are just values implementing the error interface, and the standard library distinguishes three families. Sentinel errors are exported package variables (io.EOF, fs.ErrNotExist, context.DeadlineExceeded) that you test against with errors.Is. Error types are concrete structs (*os.PathError, *strconv.NumError) carrying extra fields you extract with errors.As. Helpers — errors.New, fmt.Errorf with %w, errors.Is, errors.As, errors.Join — create and unwrap these.
The critical rule is that wrapping with fmt.Errorf("read config: %w", err) nests the original error in a chain. A bare err == io.EOF will then fail, while errors.Is(err, io.EOF) walks the chain and still matches. The same applies to types: errors.As(err, &pathErr) finds a *os.PathError no matter how deeply it is wrapped.
The three families in detail
Sentinel errors
Sentinels are package-level variables you compare using errors.Is. The most common ones across packages include:
io.EOF— clean end of a reader; not a failure in a normal read loopio.ErrUnexpectedEOF— the stream ended mid-record; this one is a real errorfs.ErrNotExist— file not found (also returned as theErrfield inside a*os.PathError)fs.ErrPermission— permission deniedcontext.Canceled— the caller cancelled the contextcontext.DeadlineExceeded— a timeout firedsql.ErrNoRows— aQueryRow().Scanfound nothing; this is expected, not a fault
Concrete error types
Types carry richer information accessible after an errors.As extraction:
*os.PathError— wraps file-system errors, with.Op(the operation name) and.Path(the affected path)*net.DNSError— DNS lookup failures, with.Name,.IsTimeout,.IsNotFound*json.SyntaxError— JSON parse problems, with.Offsetpointing to the bad byte*strconv.NumError— parse failures fromAtoi,ParseFloat, with.Funcand.Num
Error helpers
errors.New(text)— creates an opaque sentinel; use for your own package-level errorsfmt.Errorf("context: %w", err)— wraps an error while keeping the cause reachableerrors.Is(err, target)— walks the chain looking for a matching sentinelerrors.As(err, &ptr)— walks the chain looking for a matching concrete typeerrors.Join(e1, e2, ...)— combines multiple errors into one; each remains unwrappable viaerrors.Is/errors.As
Example
Wrap once, then inspect through the chain:
data, err := io.ReadAll(r)
if err != nil {
err = fmt.Errorf("read body: %w", err) // wrap, keep the cause
}
if errors.Is(err, io.ErrUnexpectedEOF) { // sentinel via chain
// truncated input
}
var pathErr *os.PathError
if errors.As(err, &pathErr) { // concrete type via chain
log.Printf("op=%s path=%s", pathErr.Op, pathErr.Path)
}
Common mistakes
Using == instead of errors.Is: Once an error is wrapped, err == io.EOF silently returns false even when EOF is the root cause. Always prefer errors.Is.
Treating sql.ErrNoRows as a fault: QueryRow().Scan returns sql.ErrNoRows when a query finds no matching row. This is normal application logic, not a database error — handle it with a dedicated branch and do not log it as an error.
Swallowing io.ErrUnexpectedEOF: Unlike io.EOF, this one means the connection or file ended before a complete record arrived. It should surface as a real error to the caller.
Note that everything runs in your browser; nothing is uploaded.