Skip to content
Go back

`errors.Is` vs `errors.As` vs `errors.AsType` — when to use which

Edit page

Three tools, three different questions.

errors.Is(err, target) — “is this this specific error?”

Compares against a sentinel value. Walks the wrap chain (so fmt.Errorf("%w: bad scheme", ErrInvalidURL) still matches).

if errors.Is(err, store.ErrInvalidURL) { ... }
if errors.Is(err, io.EOF)              { ... }
if errors.Is(err, sql.ErrNoRows)       { ... }

Use when you have a named error value and just want to know “is the failure of this kind?”

Why not ==? == only checks the outermost error. The moment someone wraps it (fmt.Errorf("%w: ...", err)), == breaks. errors.Is keeps working.

errors.As(err, &target) — “is this a kind of error, and give me the value”

Walks the wrap chain looking for an error of a specific type, and assigns it into your variable so you can read its fields.

var pathErr *os.PathError
if errors.As(err, &pathErr) {
    log.Println(pathErr.Path, pathErr.Op)
}

var urlErr *url.Error
if errors.As(err, &urlErr) {
    log.Println(urlErr.URL)
}

Use when the error carries data you care about (paths, URLs, fields, codes), not just an identity.

errors.AsType[T](err) (T, bool) — same as As, but generic

Added in Go 1.25. Identical semantics to As, just nicer ergonomics — returns the value instead of writing through a pointer.

if pathErr, ok := errors.AsType[*os.PathError](err); ok {
    log.Println(pathErr.Path)
}

Use it instead of As if you’re on 1.25+. Functionally equivalent; pick the one you find more readable. The Go community will probably trend toward AsType over time.

Mental model

Question you’re askingTool
”Is the failure mode X?” (sentinel)errors.Is
”Is the error structurally of type T and what’s inside it?”errors.As / errors.AsType

Sentinels are simpler — prefer them when an identity check is enough. Reach for As/AsType only when you actually need the fields.

In a typical store package with sentinel errors like ErrInvalidURL, ErrDuplicate, ErrNotFound, errors.Is is the right call — they’re pure sentinels with no payload. The day you wrap them with detail like fmt.Errorf("%w: scheme %q", ErrInvalidURL, parsed.Scheme), errors.Is still works — that’s the whole point of using sentinels + wrapping.


Edit page

Next Post
CSRF Protection in Go