Error Handling in Go
In most languages an error is an exception: it "jumps" out of a function, flies up the stack, and is caught somewhere by try/catch. Go has none of that. An error is an ordinary value the function returns alongside its result, and the caller checks it immediately: if err != nil. The decision of what to do about a failure is made on the spot, not somewhere higher up the stack, and the compiler will not let you "accidentally" ignore the return if you read it.
The simplicity is deceptive. Behind if err != nil sit several mechanisms that trip people up in interviews: error is an interface with a single method, Error() string, which means you can define your own error type without a single import. Wrapping via fmt.Errorf("...: %w", err) keeps a link to the original error, and it is along this Unwrap chain that errors.Is (looks for a specific sentinel value) and errors.As (looks for an error of a concrete type) walk — you must not confuse them. And the nastiest trap is not about the library at all: a stray := in a nested block shadows the outer variable, and the error quietly vanishes. This topic dissects error handling layer by layer — from the interface to shadowing.
Topic Map
- The error interface —
erroris a built-in interface with one method,Error() string; any type with that method is an error, and you can define your own with no imports. - Errors as values — an error is returned as an ordinary value (usually last), checked with
if err != nil, and sentinel errors are declared as variables. - errors.Is and errors.As —
%wwraps an error into anUnwrapchain;errors.Islooks for a value in it,errors.Asfor a type. - Error shadowing — a stray
:=in a nested scope creates a new variable and loses the outer error;go vetcatches it.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
Expecting try/catch and exceptions | Go has no exceptions for ordinary errors — an error is a return value |
| Thinking a custom error type needs an import | error is a built-in interface; an Error() string method is enough |
| Returning the error not as the last value | Breaks the idiom; readers expect (result, error) |
Wrapping with %v instead of %w | The Unwrap chain is lost, and errors.Is/errors.As stop matching |
Confusing errors.Is and errors.As | Is compares to a value, As extracts by type — not interchangeable |
Comparing a wrapped error with == | After wrapping, err == ErrNotFound is false; you need errors.Is |
A stray := in a nested block | Shadows the outer variable — the error quietly vanishes |
Hard-returning nil on the error path | The failure is swallowed; a false "success" goes upward |
Interview Relevance
Error handling is a fundamental Go interview topic at every level. The interviewer checks not knowledge of the word "error" but a working model: do you understand that an error is a value, and do you command the mechanics of wrapping and unwrapping?
What interviewers usually check:
- What the
errortype is and what "returning errors as values" means — an interface with one method, checked viaif err != nil. - How to define your own error type with no imports and why it satisfies
errorimplicitly. - How
%w,errors.Is, anderrors.Aswork together, and the difference betweenIsandAs. - Why a wrapped error cannot be compared with
==and whyerrors.Asis needed to reach the fields of a typed error. - Why a function can return a
nilerror even on failure — a hardreturn nilor:=shadowing.
A typical wrong answer: "errors.Is and errors.As are the same thing, just different syntax." That opens a discussion of how errors.Is walks the Unwrap chain and compares to a sentinel value, while errors.As searches the same chain for an error of the wanted type and assigns it into a pointer, giving access to its fields.