Go Error Handling: Annoying or Awesome?
Source: Dev.to
For weeks when I was completely new to coding, I kept wondering, “What did I get myself into?” I barely understood what I was looking at whenever I came across code like this:
file, err := os.Open("data.txt")
if err != nil {
log.Fatal(err)
}
data, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
var result MyStruct
err = json.Unmarshal(data, &result)
if err != nil {
log.Fatal(err)
}
It seemed absurd to use nine lines just for error checking.
The Problem with Go Error Handling
Early tutorials on YouTube covered error checking in Python and JavaScript, but none felt relatable for Go. I ended up copying snippets without fully understanding them, simply writing:
if err != nil {
log.Fatal(err)
}
I wasn’t even reading the error half the time; I just hoped log.Fatal(err) would never run.
Ignoring Errors Leads to Bugs
Later, while building a CLI tool that reads config files, makes API calls, and writes output to disk, I introduced a subtle bug:
data, _ := json.Marshal(payload)
Using the blank identifier (_) to ignore the error caused the API call to fail silently because the marshaled body was empty. A single error message would have saved me a lot of debugging time.
Errors Are Values, Not Exceptions
In Go, errors are ordinary values returned alongside results. When a function can fail, it returns two things: the result and an error. Writing if err != nil is essentially asking, “Something could go wrong here. What do you want to do about it?” Once I embraced this mindset, errors became clear decision points.
Adding Context with fmt.Errorf
Initially I logged errors directly:
log.Fatal(err) // vague message
The fix was to wrap errors with context using fmt.Errorf:
data, err := io.ReadAll(file)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
The %w verb preserves the original error, allowing inspection while providing a helpful description of what the program was trying to do.
Editor Snippet for the Boilerplate
To reduce friction, I set up a snippet in VS Code. Typing ife and pressing Tab expands to:
if err != nil {
return err
}
The official Go extension includes this snippet by default—just check your snippet settings. Typing the pattern dozens of times a day becomes painless.
Conclusion – Annoying or Awesome?
Both. At first, the verbosity feels annoying; once you understand why it exists, it’s awesome. Go deliberately makes error handling visible and local, preventing silent failures that can crash programs in unexpected ways. Every if err != nil is a decision point baked directly into your code, so you always know where an error originated and must decide how to handle it.
For a first language, this proved to be a surprisingly good learning environment. I couldn’t ignore problems—Go wouldn’t let me. And when things went wrong, the error was almost always exactly where Go said it was.
If you’re learning Go right now, what part of the language caught you off guard the most? Drop it in the comments.