WalzoneInterview Prep
📞 Interviewing soon? Practice with a realistic AI mock phone interview — it calls you, then scores you. First 15 min FREE →

Go · Expert · question 70 of 100

How do you handle complex error propagation in a Go application?

📕 Buy this interview preparation book: 100 Go questions & answers — PDF + EPUB for $5

Error propagation is an essential aspect of designing a robust and maintainable Go application. Complex error propagation can be managed by following some best practices and using features in the Go language effectively. Here’s a brief guide on how to handle complex error propagation in a Go application:

1. **Use meaningful error types**: Instead of using simple error strings, define custom error types to carry relevant information about the error. An error type can be a struct that implements the ‘error‘ interface by providing an implementation for the ‘Error() string‘ method:

type CustomError struct {
    Code    int
    Message string
}

func (e *CustomError) Error() string {
    return fmt.Sprintf("Code: %d, Message: %s", e.Code, e.Message)
}

2. **Wrap errors**: When you need to add context to an error, wrapping the error with additional information can be useful. One way to do this in Go is by using the ‘fmt.Errorf‘ function from the ‘fmt‘ package and the ‘%w‘ verb to wrap the error.

func doSomething() error {
    if err := performTask(); err != nil {
        return fmt.Errorf("failed to perform task: %w", err)
    }
    return nil
}

3. **Use error sentinel values and error variables**: Sentinel values are pre-defined error values used to convey specific error situations. For example:

var ErrNotFound = errors.New("not found")

func findItem(id int) (Item, error) {
    // ...
    if !found {
        return Item{}, ErrNotFound
    }
    // ...
}

To check for these sentinel error values, you can use the equality check (‘==‘):

item, err := findItem(id)
if err == ErrNotFound {
    // handle the "not found" error
}

4. **Is and As methods**: From Go 1.13, the ‘errors‘ package provides the ‘errors.Is‘ and ‘errors.As‘ functions for working with wrapped errors.

‘errors.Is‘ is used for comparing an error to a sentinel error value:

item, err := findItem(id)
if errors.Is(err, ErrNotFound) {
    // handle the "not found" error
}

‘errors.As‘ is used for checking if an error is of a certain type, and it assigns the error value to the target pointer:

item, err := findItem(id)
var customErr *CustomError
if errors.As(err, &customErr) {
    // handle the error based on customErr.Code or customErr.Message
}

5. **Categorize errors using error groups**: In more complex scenarios, you might want to categorize errors. This can be useful for handling multiple errors of the same type or for grouping related errors. You can create error groups by implementing custom error types with associated methods.

Here’s an example of an error group with accompanying ‘Is‘ and ‘As‘ methods:

type GroupError struct {
    Errors []error
}

func (ge *GroupError) Error() string {
    var sb strings.Builder
    for _, err := range ge.Errors {
        sb.WriteString(err.Error())
        sb.WriteString("n")
    }
    return sb.String()
}

func (ge *GroupError) Is(target error) bool {
    _, ok := target.(*GroupError)
    return ok
}

func (ge *GroupError) As(target interface{}) bool {
    if groupError, ok := target.(**GroupError); ok {
        *groupError = ge
        return true
    }
    return false
}

These practices and features will help you handle complex error propagation in your Go applications effectively. By employing these techniques, you can create a maintainable and clear error-handling strategy that scales well with the complexity of your application.

Reading is step one. Saying it out loud is the interview. Our AI interviewer calls your phone and runs a realistic Go interview — then scores it.
📞 Practice Go — free 15 min
📕 Buy this interview preparation book: 100 Go questions & answers — PDF + EPUB for $5

All 100 Go questions · All topics