Errors (Basics)
Errors as values in Go — returning and checking errors, errors.New and fmt.Errorf, the `if err != nil` idiom, and why an error is an ordinary value, not an exception.
4 questions
JuniorTheoryVery commonHow does a Go function report a failure to its caller?
How does a Go function report a failure to its caller?
It returns an error as the last return value, alongside any normal results. A nil error means success; a non-nil error means failure and the other results may be unusable. The caller checks the error and decides what to do — there are no exceptions for ordinary errors, so failures travel through normal return values, not a separate throw/catch channel.
Common mistakes
- ✗Expecting Go to throw exceptions for ordinary failures instead of returning an error
- ✗Using the other return values after a non-nil error, when they may be invalid
- ✗Treating a non-nil error as success because the function still returned values
Follow-up questions
- →What value should the other results hold when the function returns a non-nil error?
- →When is
panicthe right tool instead of returning an error?
MiddleTheoryVery commonWhy does idiomatic Go check if err != nil right after each call?
Why does idiomatic Go check if err != nil right after each call?
Because errors are values, control flow is explicit: you must look at each one yourself. Checking immediately and returning early stops you from acting on a result that may be invalid after a failure. It also keeps the happy path unindented — the success branch flows straight down while error returns peel off. Ignoring the error with _ silently hides the failure, the bug this idiom prevents.
Common mistakes
- ✗Deferring all error checks to the end instead of right after each call
- ✗Discarding an error with
_and acting on a result that may be invalid - ✗Nesting the happy path inside the error branch, inverting the idiom
Follow-up questions
- →When is it genuinely safe to ignore a returned error with
_? - →How does an early
returnon error keep the happy path unindented?
JuniorTheoryCommonWhy is an error in Go a value rather than an exception?
Why is an error in Go a value rather than an exception?
An error in Go is an ordinary interface value returned from a function. Because it is just a value, you store it, pass it around, compare it, and inspect it with normal code — no special try/catch syntax. This makes failure handling explicit and visible at every call site. panic is reserved for truly exceptional, unrecoverable situations, not for routine errors.
Common mistakes
- ✗Treating Go errors like thrown exceptions routed automatically up the stack
- ✗Reaching for
panicto signal ordinary, expected failures - ✗Assuming errors need special syntax instead of normal value-handling code
Follow-up questions
- →What exactly does the built-in
errorinterface require a type to implement? - →When does a
panicmake sense, and how doesrecoverfit in?
JuniorTheoryCommonHow do you create a new error value in Go, with or without context?
How do you create a new error value in Go, with or without context?
Use errors.New("message") for a fixed, static message, or fmt.Errorf("context: %v", x) when you need to format details into the text. Both return a value satisfying the error interface. fmt.Errorf with the %w verb additionally wraps an existing error, so callers can later unwrap it with errors.Is or errors.As to inspect the original cause.
Common mistakes
- ✗Thinking
fmt.Errorfonly prints text rather than returning anerrorvalue - ✗Using
%vwhen wrapping is intended, losing the unwrap chain that%wkeeps - ✗Reaching for
panicto build an error instead oferrors.New/fmt.Errorf
Follow-up questions
- →What is the difference between formatting an error with
%vand with%w? - →How do
errors.Isanderrors.Asuse the chain that%wbuilds?