Error Handling in Go
Error handling in Go — wrapping, errors.Is/As, sentinel errors, defer cleanup, retry policies.
5 questions
JuniorTheoryVery commonWhat is the error type in Go, and what does it mean to return errors as values?
What is the error type in Go, and what does it mean to return errors as values?
error is a built-in interface with one method, Error() string. Any type implementing it is an error. Functions return error as an ordinary return value (usually last); the caller checks if err != nil instead of catching exceptions.
Common mistakes
- ✗Thinking Go has exceptions for normal errors —
panic/recoveris for unrecoverable bugs, not control flow - ✗Ignoring the returned
errorwith_instead of checkingif err != nil - ✗Assuming
erroris a struct rather than an interface, so any custom type withError() stringqualifies
Follow-up questions
- →How do you create a simple error value with
errors.Neworfmt.Errorf? - →What is a sentinel error and how is it compared with
errors.Is?
MiddleCodeCommonHow do %w wrapping and errors.Is / errors.As work together in Go?
How do %w wrapping and errors.Is / errors.As work together in Go?
fmt.Errorf("...: %w", err) wraps err, adding context while keeping a link to it via an Unwrap chain. errors.Is walks that chain to match a sentinel value; errors.As walks it to find an error of a concrete type and assign it into a target pointer.
Common mistakes
- ✗Using
%vinstead of%w, which loses theUnwraplink soerrors.Is/errors.Ascan no longer match through it - ✗Comparing wrapped errors with
==instead oferrors.Is, which fails once context is added - ✗Passing a non-pointer to
errors.As, which panics — the target must be a pointer to an error-implementing type
Follow-up questions
- →Why does wrapping with
%wrequire the error to implement anUnwrapmethod underneath? - →When would you wrap with multiple
%wverbs in onefmt.Errorfcall?
MiddleDebuggingOccasionalWhy does this load always return a nil error even when parse fails?
Why does this load always return a nil error even when parse fails?
The function hard-codes return cfg, nil, so parse's error is swallowed. A related classic is cfg, err := parse(data) with := in an inner scope, which shadows the outer cfg so the parsed value is lost. Fix: return the error on failure and avoid accidental := shadowing; go vet catches it.
Common mistakes
- ✗Hard-coding
return cfg, nilinstead of returning the real error - ✗Using
:=in an inner scope, silently shadowing the outer variable - ✗Assuming the success-only
ifbranch meansparsecannot fail
Follow-up questions
- →How does
:=decide whether to create a new variable or reuse an outer one? - →What does
go vet's shadow analysis report on this function?
SeniorDebuggingOccasionalFix the bugs in this HandleBookingOrder booking handler.
Fix the bugs in this HandleBookingOrder booking handler.
Change the signature to (*Receipt, error) and wrap each failure with %w. Move UnlockUser into a defer that captures and wraps its own error via a named return. Give BookingServiceError an Error() string, and in the loop use errors.As to recover it and check TryAgain instead of a bare field on the interface error.
Common mistakes
- ✗Reading
err.TryAgaindirectly — the loop holdserras anerrorinterface, so the concrete field is unreachable withouterrors.As - ✗Calling
UnlockUserinline instead ofdefer, so an earlyreturnon a booking failure leaks the user lock - ✗Returning
nilon the lock-failure path of a(*Receipt, error)function, so the caller sees neither a receipt nor an error
Follow-up questions
- →Why must the deferred unlock use a named return parameter to surface its error?
- →How would you add a bounded retry count and backoff to the
TryAgainloop?
JuniorCodeRareReturn a custom error without importing any package
Return a custom error without importing any package
error is a built-in interface with one method, Error() string. So you define a struct, give it an Error() string method, and return a pointer to it — no import needed. func (e *customError) Error() string { return "custom error" }, then return &customError{}. Any type implementing that one method satisfies error implicitly.
Common mistakes
- ✗Thinking
errorlives in a package rather than being a built-in interface - ✗Believing
erroris an alias forstringthat you can cast to - ✗Expecting Go to auto-generate
Error()from a struct field
Follow-up questions
- →Why use a pointer receiver for
Error()rather than a value receiver here? - →How would adding fields to your error type let callers inspect it with
errors.As?