How does error handling work in Go and why does it not use exceptions?
Learn how Go handles errors as return values with the error interface, why it avoids exceptions, and how to wrap and inspect errors idiomatically.
Expected Interview Answer
Go handles errors as ordinary values: functions return an error as their last return value, and the caller checks it explicitly with an if err != nil block rather than catching thrown exceptions.
The built-in error interface has a single Error() string method, so any type can be an error. This makes failures part of a function's signature and control flow, keeping the path a program takes explicit and visible. Go deliberately avoids exceptions because they create invisible jump paths that make it hard to reason about which lines can fail; errors-as-values force each failure to be handled, wrapped with %w, or explicitly ignored right where it happens.
- Failure paths are explicit and visible in code
- No hidden control-flow jumps to reason about
- Errors compose and wrap cleanly with fmt.Errorf and %w
- The compiler and linters can flag unchecked errors
- Encourages handling problems close to where they occur
AI Mentor Explanation
Handling errors as return values is like a fielder who, after every ball, immediately signals the umpire whether the ball was clean or a no-ball before the next delivery is bowled. Nothing proceeds until that check is acknowledged out loud. Exceptions would be like the ball silently going out of bounds and someone three overs later shouting 'that was illegal' — Go refuses that, insisting each delivery reports its own status on the spot.
Step-by-Step Explanation
Step 1
Return error last
Design functions to return their result plus an error as the final value, e.g. (T, error).
Step 2
Check immediately
At the call site, test if err != nil before using the returned value.
Step 3
Handle or propagate
Either resolve the error locally or return it up the stack, wrapping it with fmt.Errorf and %w for context.
Step 4
Inspect wrapped errors
Use errors.Is to compare against sentinel errors and errors.As to extract a concrete error type.
Step 5
Reserve panic
Use panic only for truly unrecoverable programmer errors, not for ordinary expected failures.
What Interviewer Expects
- Knowing errors are ordinary values returned from functions
- The error interface and its Error() string method
- Why Go avoids exceptions for control flow
- Wrapping with %w and inspecting via errors.Is / errors.As
- When panic and recover are appropriate versus returning errors
Common Mistakes
- Ignoring returned errors with the blank identifier by habit
- Using panic and recover as a general exception mechanism
- Comparing wrapped errors with == instead of errors.Is
- Losing context by returning err without wrapping it
- Checking err only sometimes instead of after every fallible call
Best Answer (HR Friendly)
“In Go, when something can go wrong a function simply hands back an extra error value, and the caller checks it right away instead of relying on hidden try/catch. Go chose this so every possible failure is visible in the code, making programs easier to follow and less likely to hide bugs.”
Code Example
package main
import (
"errors"
"fmt"
"os"
)
var ErrNotFound = errors.New("config not found")
func loadConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("loadConfig %q: %w", path, err)
}
return data, nil
}
func main() {
data, err := loadConfig("app.yaml")
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Println("no config, using defaults")
return
}
fmt.Println("fatal:", err)
return
}
fmt.Printf("loaded %d bytes\n", len(data))
}Follow-up Questions
- What is the difference between errors.Is and errors.As?
- How does %w differ from %v when formatting an error?
- When is it appropriate to use panic instead of returning an error?
- How would you define a custom error type in Go?
- What are sentinel errors and what are their drawbacks?
MCQ Practice
1. What is the idiomatic way to signal a failure from a Go function?
Go functions return an error (conventionally the last return value), and callers check it explicitly.
2. Which verb wraps an underlying error so errors.Is can still match it?
%w in fmt.Errorf wraps the error, preserving the chain for errors.Is and errors.As.
3. Why does Go avoid exceptions for normal error handling?
Exceptions create invisible control-flow jumps; Go prefers explicit, visible error values.
Flash Cards
How are errors represented in Go? — As ordinary values implementing the error interface, returned from functions and checked with if err != nil.
What method does the error interface require? — Error() string — any type implementing it is an error.
How do you add context to an error? — Wrap it with fmt.Errorf using the %w verb, then inspect later with errors.Is / errors.As.
Why no exceptions in Go? — To keep failure paths explicit and visible instead of hidden control-flow jumps.