ProgrammingBackend Developer

How is error handling implemented in Go, why did Go choose its error handling model, and what are the best practices for using errors in everyday programming?

Pass interviews with Hintsage AI assistant

Answer.

Background:

Go differs from many languages in its approach to error handling, where errors are values, not exceptions. This design was chosen for transparency and simplicity: whenever something might go wrong, a function explicitly returns the error as a second return value.

Problem:

Many beginners try to apply familiar try-catch patterns to Go, getting lost in the numerous error checks or failing to use error wrapping to convey context. This leads to loss of informativeness and difficult debugging.

Solution:

In Go, a function that can return an error is declared like this:

func doSomething() (ResultType, error) { // ... if somethingWrong { return nil, errors.New("something went wrong") } return result, nil }

Error checking is the responsibility of the caller:

res, err := doSomething() if err != nil { log.Fatalf("process failed: %w", err) }

Go 1.13 and above allow for "wrapping" errors using fmt.Errorf("%w", err) for building error chains. This improves diagnostics and promotes best practice for contextual error description.

Key Features:

  • Errors are values returned by functions (no try/catch)
  • Custom error types can be created (using the error interface)
  • Error wrapping makes debugging and logging clearer

Tricky Questions.

Why create your own error types when you can just return errors.New("...")?

The correct answer: custom error types allow not only to convey the error message but also to retain additional information for further processing (e.g., return codes, context) and to implement finer handling through type assertion.

Example:

type NotFoundError struct { Resource string } func (e NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) } // Check if _, ok := err.(NotFoundError); ok { // handle not found error }

Is panic a good replacement for error for critical errors?

No, panic in Go is used only in truly hopeless situations — for example, when there are errors in the program itself (program invariants), but not for ordinary failures (e.g., failed to open a file). Using panic to signal routine errors is an evil and leads to unreadable, unmanageable code.

What happens if error handling (err) is skipped in nested functions?

The error "gets lost," the code continues executing, which can lead to the spread of erroneous states. It is always important to properly handle each error return.

Common Errors and Anti-Patterns

  • Ignoring returned errors using _ = ...
  • Using panic/recover instead of error for flow control
  • Redirecting uncaught errors without contextual messages

Real-Life Example

Negative Case

Each function simply returns errors.New(...), errors are not wrapped, error types vary, but the handling is always the same — logged and thrown. As a result, log files are filled with uninformative messages, making it impossible to trace them back to the root cause.

Pros:

  • Fast code writing
  • Less code for handling

Cons:

  • Poor traceability
  • No ability to filter or distinguish errors by type

Positive Case

Error wrapping is used via fmt.Errorf("%w", err), custom errors with useful fields, and checks through errors.Is()/errors.As(). As a result, logging with details can be done, business errors can be separated from environmental failures, and reliable retry logic can be implemented.

Pros:

  • Convenient debugging
  • Cleaner code
  • Flexible error handling

Cons:

  • More code and boilerplate logic
  • Need to maintain error types