Backend Development 15 min read

Error Handling and ErrGroup Patterns in Go

The article explains Go’s built‑in error interface, distinguishes errors, exceptions and panics, presents three handling patterns—classic returns, stateful objects, and functional deferred execution—shows how to wrap errors for context, and demonstrates using the errgroup concurrency primitive (including an extended version) for safe parallel processing.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Error Handling and ErrGroup Patterns in Go

In Go, error handling is a fundamental part of building reliable services. The language defines a built‑in error interface with a single method Error() string , which serves as the standard way to represent recoverable errors.

Unlike exceptions in other languages, Go distinguishes three concepts:

Error : an expected problem such as a failed database connection, which should be handled explicitly by the caller.

Exception : an unexpected condition (e.g., nil‑pointer dereference) that triggers a panic .

panic : used for unrecoverable situations; production code should avoid calling panic except during startup failures.

The article outlines three common patterns for handling errors in Go:

1. Classic Go logic (returning error )

type ZooTour interface {
    Enter() error
    VisitPanda(panda *Panda) error
    Leave() error
}

func Tour(t ZooTour, panda *Panda) error {
    if err := t.Enter(); err != nil {
        return errors.WithMessage(err, "Enter failed.")
    }
    if err := t.VisitPanda(panda); err != nil {
        return errors.WithMessage(err, "VisitPanda failed.")
    }
    // ...
    return nil
}

2. Storing error inside an object (stateful handling)

type ZooTour interface {
    Enter() error
    VisitPanda(panda *Panda) error
    Leave() error
    Err() error
}

func Tour(t ZooTour, panda *Panda) error {
    t.Enter()
    t.VisitPanda(panda)
    t.Leave()
    if err := t.Err(); err != nil {
        return errors.WithMessage(err, "ZooTour failed")
    }
    return nil
}

3. Functional programming style (deferred execution)

type Walker interface { Next MyFunc }

type SliceWalker struct {
    index int
    funs []MyFunc
}

func NewEnterFunc() MyFunc {
    return func(t ZooTour) error { return t.Enter() }
}

func BreakOnError(t ZooTour, walker Walker) error {
    for {
        f := walker.Next()
        if f == nil { break }
        if err := f(t); err != nil { return err }
    }
    return nil
}

These patterns can be chosen based on the complexity of the business logic: simple sequential checks, stateful pipelines, or more abstract functional flows.

For richer error information, Go developers often use error‑wrapping libraries such as github.com/pkg/errors . The library provides functions like New , WithMessage , WithStack , Wrapf , and Cause to attach stack traces and additional context.

// Create a new error with stack trace
err := errors.New("something went wrong")

// Add context to an existing error
err = errors.WithMessage(err, "while processing request")

// Retrieve the root cause
root := errors.Cause(err)

In layered architectures (DAO → Service → Controller), a typical error‑handling flow looks like:

// controller
if err := mode.ParamCheck(param); err != nil {
    log.Errorf("param=%+v", param)
    return errs.ErrInvalidParam
}
return mode.ListTestName("")

// service
_, err := dao.GetTestName(ctx, settleId)
if err != nil {
    log.Errorf("GetTestName failed. err: %v", err)
    return errs.ErrDatabase
}

// dao
if err != nil {
    log.Errorf("GetTestDao failed. query: %s error(%v)", sql, err)
    return err
}

Key take‑aways for error handling include:

Log every error with appropriate severity; use %+v to print stack traces.

Wrap errors at the point of propagation to preserve context.

Prefer error handling over panic for business‑level failures.

The article also introduces errgroup , a concurrency primitive from golang.org/x/sync/errgroup , which simplifies running multiple goroutines and collecting the first error.

type Group struct {}

func (g *Group) WithContext(ctx context.Context) (*Group, context.Context)
func (g *Group) Go(f func() error)
func (g *Group) Wait() error

Typical usage:

func TestErrgroup() {
    eg, ctx := errgroup.WithContext(context.Background())
    for i := 0; i < 100; i++ {
        i := i // capture loop variable
        eg.Go(func() error {
            time.Sleep(2 * time.Second)
            select {
            case <-ctx.Done():
                fmt.Println("Canceled:", i)
                return nil
            default:
                fmt.Println("End:", i)
                return nil
            }
        })
    }
    if err := eg.Wait(); err != nil {
        log.Fatal(err)
    }
}

Bilibili’s extended errgroup adds features such as concurrency limiting, panic recovery, and a task queue. Its core structure includes a channel of functions, a slice for overflow tasks, and a context with cancel function.

type Group struct {
    err     error
    wg      sync.WaitGroup
    errOnce sync.Once
    workerOnce sync.Once
    ch         chan func(ctx context.Context) error
    chs        []func(ctx context.Context) error
    ctx    context.Context
    cancel func()
}

func WithContext(ctx context.Context) *Group { return &Group{ctx: ctx} }

func (g *Group) Go(f func(ctx context.Context) error) {
    g.wg.Add(1)
    if g.ch != nil {
        select { case g.ch <- f: default: g.chs = append(g.chs, f) }
        return
    }
    go g.do(f)
}

When using the extended version, developers should be aware of potential deadlocks if the task slice grows without proper synchronization, and that the internal WaitGroup only tracks the producer side, not the consumer pool.

Overall, the article emphasizes that robust error handling in Go involves clear distinction between recoverable errors and unrecoverable panics, systematic error wrapping to retain context, and using concurrency primitives like errgroup to manage parallel work safely.

Backend DevelopmentConcurrencyGoLoggingerror handlingerrgroupError Wrapping
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.