DEV Community

Cover image for Go Error Handling: Annoying or Awesome?
Ryan Kikayi
Ryan Kikayi

Posted on

Go Error Handling: Annoying or Awesome?

For weeks when I was completely new to coding,I would always keep wondering; "What did I get myself into?", barely understanding what I'm looking at whenever I come across code that looked 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)
}
Enter fullscreen mode Exit fullscreen mode

And for some reason it just did not make any sense to me at first; what do you mean we're using 9 lines of code just for error checking - has to be a joke?!
Over at YouTube, no tutorial was relatable. I mean there were tutorials on error checking for python, and javascript, but the concept wasn't just sticking.
Eventually, I ended up just copying chunks of code that I barely understood and pasted them into mine. Function returns two things, assign them to a variable and err. Basically pasting the if err != nil block was what I knew I was doing, the reason why? Not so much.
I wasn't reading the error half of the time, I was just calling log.Fatal(err) hoping it would never have to run.

Later on after getting comfortable with the language, I decided to work on a personal project - a CLI tool that reads config files, makes API calls, and writes its output to disk. Unfortunately, it broke in production, the problem;

data, _ := json.Marshal(payload)
Enter fullscreen mode Exit fullscreen mode

I had used a blank identifier _ to ignore the error completely - a habit I discovered to avoid having to write the check. This caused an API call to fail silently because the marshal was sending an empty body, and the API was rejecting it. And there I was, trying to debug something a single error message would have told me immediately.
I realized, in Go errors are not exceptions, they're values - like regular data, a functions gives back to you. When you call a function that can fail, it returns two things; the result, and an error. Go hands you the error and trusts you to deal with it. So basically, writing if err != nil is like saying "Something could go wrong here. What do you want to do about it?" in Go. Once I finally understood that I started seeing them as decision points.

So how do you actually make peace with it?

Early on, I used to write my errors the same: log.Fatal(err). When something broke, I'd get a vague message like unexpected end of JSON input with zero context about where in my program it came from.
The fix was wrapping errors with context using fmt.Errorf :

data, err := io.ReadAll(file)
if err != nil {
    return fmt.Errorf("failed to read config file: %w", err)
}
Enter fullscreen mode Exit fullscreen mode

The %w keeps the original error intact so you can still inspect it, but now every error message tells you what your program was trying to do when it failed. That one change made debugging significantly less painful.
The second thing that helped was setting up a snippet in my editor. In VS Code, typing ife and hitting Tab expands to:

if err != nil {
    return err
}
Enter fullscreen mode Exit fullscreen mode

The official Go extension already includes this — check your snippet settings if you haven't already. It sounds like a small thing, but when you're writing the pattern twenty times a day, not having to type it from scratch every time genuinely reduces the friction.

So — annoying or awesome?

Honestly? Both. Annoying at first, awesome once you understand why it exists.
Go made a deliberate choice to make error handling visible and local, rather than something that silently bubbles up through your stack and crashes things in ways you didn't expect. Every if err != nil is a decision point baked directly into your code. You always know where an error came from. You always have to decide what to do with it.
For a first language, that turned out to be a surprisingly good environment to learn in. 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.
Would I have picked a different first language if someone had warned me about this pattern? Maybe. Am I glad I stuck with Go? Absolutely.

If you're learning Go right now, I'm curious — what part of the language caught you off guard the most? Drop it in the comments.

Top comments (2)

Collapse
 
csm18 profile image
csm

Normally, I write helper functions that wrap the logic in them and give us clean api to use, like this for example:

func FileExists(filename string) bool {
    _, err := os.Stat(filename)
    return err == nil
}

func CreateFile(filename string, content string) {
    err := os.WriteFile(filename, []byte(content), 0644)
    if err != nil {
        fmt.Println("error: While creating file ",filename)
        os.Exit(1)
    }
}

func PrintError(message string) {
    fmt.Println(message)
    os.Exit(0)
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
xiaoming_nian_94953c8c9b8 profile image
Andy Nian

Dealing with Go's error handling can be tough, especially when you're new and the if err != nil pattern seems excessive. Ignoring errors with _ can cause problems later, as you experienced. I faced similar issues while preparing for coding interviews. I've been using prachub.com for some Go-specific technical rounds, and they reflect real-world interview scenarios better than most other prep sites. The explicit error handling in Go can be a strength once you get the hang of it through practice.