Skip to main content
Preslav Rachev

Image Source: Midjourney

The following post is a spiritual successor to an essay on error handling in Go I wrote in 2023. It’d recommend anyone who hasn’t read it to check that one out first.


Have you ever thought of what happens with errors in your Go apps? From the point of view of your Go application, there are three distinction points where something happens with an error:

  1. An error “originates,” i.e., occurs for the first time (like, the last time you actually used errors.New).
  2. A function under our control captures an error and decides to either:
    1. cut the execution of our code short (panic, early return, clean up)
    2. or what is much more often, wraps the captured error with some context and returns it to the previous caller in the chain.
  3. An error “bubbles up” to the outer-most caller. Unless something exceptional happened along the way (e.g. someone panicked ) this is usually the main package, if not the main function itself. This is the usual place to try and do some peeking into the error, but honestly, in 99% of the time, this part would simply:
    1. write the complete error message out for debugging.
    2. based on the situation, decide whether the error is severe enough to keep the app running. Maybe it’s better to just kill the application and let your infrastructure restart it. For example, if the error occurred during an HTTP request, this part may show the user a standard error page. However, if it was something that happened during the startup of the application (e.g., one was not able to connect to the database), I’d simply log.Fatal the error and let my infrastructure setup take care of restarting services in order.

From experience, unless you are building a library, you will likely not spend a lot of time introducing new errors to your application. Like every other Go application out there, yours will either use the standard library directly or rely on a library that uses it. Hence, the chances of dealing with an existing error when using a file or a database connection are far higher than when your app needs to invent a new one out of thin air.

Don’t believe me? Check out the number of occasions errors.New appears in the Kubernetes codebase, vs. those of fmt.Error(f). More than twice more, heh?

This brings me to the biggest point where I think many Go developers get things wrong. And no, I am not talking about this:

if err != nil {
	return err
}

This is plain useless. I stressed the importance of wrapping all errors with useful context, and I still firmly stand by that. What I believe people are getting wrong is the messaging part of the context. Too often, I see messages trying to tell me what went wrong. Instead, they should have told me what they tried to do just before things got messed up.

Indeed, if you are finding yourselves in pt 1. and returning a brand new error, it’s OK to use the “s*** got wrong” narrative:

return errors.New("DB connection failed")

However, assuming you will find yourselves in pt. 2 much more often, you will get the error above while trying to perform a DB operation, and what do you do? Like most people, you would wrap that with something like:

return fmt.Errorf("DB failure: %w", err)

But so will the next 10 callers above you. In the end, the message the unfortunate developer on duty gets is something like this:

ERROR: DB failure: DB failure: DB failure: failed connecting to the DB: DB failure: "DB connection failed"

Not helpful at all. Why don’t you change the narrative and tell me what the code tried to do before it failed? In other words, if you are in pt 2, don’t use the “s*** got wrong” pattern, but the more informative “doing and so and so.” Everyone knows that if they see an error log, it is because something went wrong, so they will thank you multiple times for not using the word failed and so on time and again.

For a great illustration, you can check out this section of my previous post.

Have something to say? Join the discussion below 👇

Want to explore instead? Fly with the time capsule 🛸

You may also find these interesting