Error Wrapping (errors.Is/As, %w)

Go errors are just values, and since Go 1.13 you can wrap them to add context without losing the original. By the end you'll wrap with %w , match wrapped errors with errors.Is , extract custom error types with errors.As , and understand why == isn't enough.

Learn Error Wrapping (errors.Is/As, %w) in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…

Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

Use %w (wrap) when callers may want to inspect the cause; use %v (format only) when you just want the text and no link back.

1️⃣ Wrapping with %w

As an error travels up through your call stack, each layer can add context. The verb %w in fmt.Errorf wraps the original error: the message gains your context, but the original stays reachable underneath. Compare with %v , which only embeds the text and severs the link.

Wrapping composes — each layer adds context, and errors.Is still sees all the way down to the root cause.

Your turn. Replace the blank with the verb that wraps the error so errors.Is can still find it.

2️⃣ errors.Is vs ==

Before wrapping, people compared errors with == . That only checks the outermost error, so a wrapped error never matches. errors.Is walks the entire chain, which is exactly what you want. errors.Unwrap peels off one layer if you ever need to step through manually.

3️⃣ Custom Types & errors.As

Sometimes an error needs to carry data , not just a message — which field failed, an HTTP status, a retry-after time. Define a type with an Error() string method and it's an error. To get that concrete type back out of a wrap chain, use errors.As , which sets a pointer you provide.

These lines define a sentinel, wrap it with context, and check for it. Put them in the correct order:

B, C, D, A, E. Declare the sentinel, wrap it with %w to add context, then test with errors.Is and print inside the matching if block. The sentinel must exist before you can wrap or match it.

Predict the output before revealing the answer.

false true — == compares only the outer wrapper, which is a new error value, so it's false. errors.Is walks the chain and finds ErrX , so it's true.

false — %v only formats the text of ErrX into the message; it does not wrap, so there's no link for errors.Is to follow. You must use %w to wrap.

true name — errors.As finds the *ValidationError in the chain, sets ve to point at it, and returns true. Then ve.Field reads "name".

Use %w when a caller might want to detect the underlying cause with errors.Is or errors.As . Use %v when you only want the human-readable text and don't want to expose the cause as part of your API.

Q: What's the difference between errors.Is and errors.As ?

errors.Is answers "is this specific error value in the chain?" — good for sentinels. errors.As answers "is there an error of this type?" and hands you the value so you can read its fields — good for custom error types.

Q: Should every function wrap the errors it returns?

Add context where it's useful — typically once per logical boundary (a package or major operation). Wrapping at every single call adds noise. Aim for a message trail that reads like a sentence describing what was being attempted.

Yes. Add an Is(target error) bool or As(target any) bool method and the standard functions will call it, letting you define custom matching (for example, treating any 404 as a not-found error).

No blanks — just a brief. Write a parser that wraps a sentinel error with context, then detect it from main with errors.Is . This is precisely how real Go programs propagate and classify failures.

Practice quiz

Which fmt.Errorf verb WRAPS an error so it stays reachable?

  • %v
  • %s
  • %w
  • %e

Answer: %w. %w wraps the error, keeping the original reachable; %v only embeds the text and severs the link.

What does errors.Is(err, target) check?

  • Whether target appears anywhere in the wrap chain
  • Only the outermost error
  • The error's type
  • If err is nil

Answer: Whether target appears anywhere in the wrap chain. errors.Is walks the entire wrap chain looking for a match, not just the outermost error.

Given wrapped := fmt.Errorf("step 1: %w", ErrClosed), what does wrapped == ErrClosed return?

  • true
  • a panic
  • nil
  • false

Answer: false. == compares only the top wrapper, which is a new value, so it is false. Use errors.Is to match wrapped errors.

For that same wrapped error, what does errors.Is(wrapped, ErrClosed) return?

  • false
  • true
  • an error
  • nil

Answer: true. errors.Is walks the chain and finds ErrClosed underneath the wrap, so it returns true.

If you wrap with %v instead of %w, will errors.Is find the original?

  • No — %v does not create a link
  • Yes
  • Only one level deep
  • Only for sentinels

Answer: No — %v does not create a link. %v only formats the text and does not wrap, so there is no chain for errors.Is to follow — it returns false.

What does errors.As(err, &v) do?

  • Compares two errors
  • Unwraps one layer
  • Extracts a value of v's type from the chain and sets v
  • Creates a sentinel

Answer: Extracts a value of v's type from the chain and sets v. errors.As finds an error of the target type in the chain and sets the pointer you pass, so you can read its fields.

What does errors.Unwrap(err) do?

  • Walks the whole chain
  • Removes exactly one wrap layer
  • Returns the root cause
  • Deletes the error

Answer: Removes exactly one wrap layer. errors.Unwrap peels off exactly one layer at a time.

How do you create a simple sentinel error?

  • fmt.Errorf("x")
  • new(error)
  • error("x")
  • errors.New("x")

Answer: errors.New("x"). errors.New("x") creates a sentinel error value callers can compare against with errors.Is.

How many %w verbs are allowed in a single fmt.Errorf call?

  • Unlimited
  • Exactly one
  • Two
  • Zero

Answer: Exactly one. Only one %w is allowed per fmt.Errorf call; use multiple calls if you must wrap more than one error.

Which tool is best for a 'tell me more about it' check on a custom error type?

  • errors.Is
  • == comparison
  • errors.As
  • errors.New

Answer: errors.As. errors.As pulls out the concrete custom type so you can read its fields; errors.Is is for sentinel identity checks.