Golang handles expected failures with ordinary return values. A function that can fail usually returns a useful result and an error. The caller checks whether the error is nil before using the result.
This explicit style makes failure paths visible. Instead of hidden exceptions jumping out of a function, the code shows exactly where errors are checked, wrapped, handled, logged, or returned to the caller.
package main
import (
"fmt"
"strconv"
)
func parseLimit(value string) (int, error) {
limit, err := strconv.Atoi(value)
if err != nil {
return 0, fmt.Errorf("parse limit %q: %w", value, err)
}
return limit, nil
}
func main() {
limit, err := parseLimit("25")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("limit:", limit)
}
A sentinel error is a package-level error value used for a known condition. Common examples are not found, already exists, unauthorized, or invalid state.
Sentinel errors are useful when callers need to make decisions based on a specific failure. Use errors.Is to compare them, especially after wrapping.
package tutorials
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("tutorial not found")
func LoadTutorial(slug string) error {
return fmt.Errorf("load tutorial %q: %w", slug, ErrNotFound)
}
func HandleTutorial(slug string) {
err := LoadTutorial(slug)
if errors.Is(err, ErrNotFound) {
fmt.Println("show 404 page")
return
}
}
Use fmt.Errorf with %w to add context while preserving the original error. Context should explain what operation failed and include important values, such as an ID, filename, route, or user action.
Wrapping lets logs tell a story from high-level operation to low-level cause. The caller can still use errors.Is for known sentinel errors and errors.As for custom error types.
| Tool | Purpose |
|---|---|
fmt.Errorf("...: %w", err) | Add context while preserving the original error. |
errors.Is(err, target) | Check whether an error matches a known error value. |
errors.As(err, &target) | Extract a custom error type from an error chain. |
func SaveProfile(userID int, input ProfileInput) error {
if err := validateProfile(input); err != nil {
return fmt.Errorf("validate profile for user %d: %w", userID, err)
}
if err := repository.Save(userID, input); err != nil {
return fmt.Errorf("save profile for user %d: %w", userID, err)
}
return nil
}
A custom error type is useful when callers need structured details, not just a message. For example, a validation error may include the field name, a code, and a user-friendly message.
A type becomes an error when it implements the Error() string method. Keep the method message helpful for logs, and expose fields when code needs to make decisions.
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return e.Field + ": " + e.Message
}
func validateTitle(title string) error {
if title == "" {
return ValidationError{Field: "title", Message: "is required"}
}
return nil
}
func handle() {
err := validateTitle("")
var validationErr ValidationError
if errors.As(err, &validationErr) {
fmt.Println(validationErr.Field, validationErr.Message)
}
}
Good error messages explain the failed operation and important values. Keep messages lowercase and avoid punctuation unless the message contains multiple sentences. This convention helps wrapped errors read naturally.
Return errors at the right level. Low-level functions should describe the technical operation that failed. Higher-level functions can wrap with business context so the final error is useful in logs and debugging.
Use panic for programmer mistakes and impossible states, not for normal validation failures. A missing title in an API request should return an error response, not panic.
Some servers recover from panics at a boundary so they can log the issue and keep serving other requests. Even then, ordinary failures should still use error. Recovery is a safety net, not the main error-handling strategy.
| Situation | Use |
|---|---|
| Invalid user input | Return an error or HTTP 400 response. |
| Missing database record | Return a not-found error. |
| Impossible internal state | Panic may be acceptable. |
| Server boundary | Recover, log, and return a safe response. |
error.
err != nil before using results.
%w to preserve causes while adding context.
errors.Is and errors.As instead of string matching.
Explore 500+ free tutorials across 20+ languages and frameworks.