Error handling
Errors are values you return, check, and inspect β not exceptions you throw.
Go models failure with a returned error value; fmt.Errorf("...: %w", err) wraps it, and errors.Is/errors.As inspect the chain.
Go has no exceptions for ordinary failure. A function that can fail returns an error as its last result, and the caller checks it before using the other return values. The error type is just an interface with one method, Error() string, so anything that can describe itself as a string can be an error.
The convention is if err != nil { ... }. A nil error means success; a non-nil error means the other returns may be unusable. Because errors are plain values, you can store them, compare them, and pass them around like any other data.
Wrapping adds context
When you pass an error up the stack, you usually want to say where it came from. fmt.Errorf with the %w verb wraps an error: it produces a new error whose message prefixes the original, while keeping the original reachable underneath. This builds a chain β failed to load config: open config.json: file does not exist β without discarding the root cause.
Inspecting the chain
To branch on a specific failure, don't string-match the message. Use errors.Is(err, target) to test whether a known sentinel error (a package-level var ErrFoo = errors.New(...)) appears anywhere in the chain, even through several %w wraps. Use errors.As(err, &target) to pull out a custom error type so you can read its fields. Both walk the wrapped chain for you.
Try it 5 examples
Return an error, check it
GointroThe even call returns a nil error so the else branch prints the value, while the odd call returns a non-nil error that the caller inspects instead of the (zeroed) first return. This is the core if err != nil flow: check the error before trusting the other results.