Error handling: no exceptions as control flow
Replace exception-driven flow with explicit error values and predictable control paths.
by the end of this lesson you can
- →Returns an error rather than a boolean-only success flag
- →Handles validation failure immediately
- →Keeps the happy path linear after the error checks
Overview
This is the first difference many Python developers feel in their hands. Go surfaces failure directly in the return value and expects you to deal with it immediately.
In Python, you often
let failures bubble up through exceptions and catch them where recovery makes sense, or sometimes not at all.
In Go, the common pattern is
to return `(value, err)` and handle the unhappy path right beside the call site. The repetition is deliberate: it makes failure visible.
why this difference matters
Once the pattern clicks, Go error handling feels less like ceremony and more like a habit that keeps surprising control flow out of the codebase.
Python
try:
data = load_profile(user_id)
except ProfileError as exc:
logger.error("profile failed: %s", exc)Go
data, err := loadProfile(userID)
if err != nil {
log.Printf("profile failed: %v", err)
return err
}Deeper comparison
Python version
def render_dashboard(user_id):
try:
profile = load_profile(user_id)
stats = load_stats(user_id)
except ProfileError as exc:
logger.warning("profile unavailable: %s", exc)
return None
return build_dashboard(profile, stats)Go version
func renderDashboard(userID int) (*Dashboard, error) {
profile, err := loadProfile(userID)
if err != nil {
log.Printf("profile unavailable: %v", err)
return nil, err
}
stats, err := loadStats(userID)
if err != nil {
return nil, err
}
dashboard := buildDashboard(profile, stats)
return &dashboard, nil
}Reflect
How does Go change your sense of where failure lives in a function compared with Python exceptions?
what a strong answer notices
A strong answer points out that the failure path is visible after each operation instead of being deferred to an outer try/except block.
Rewrite
Rewrite this exception-based Python control flow into Go using explicit error returns.
Rewrite this Python
def save_user(user):
try:
validate(user)
repository.save(user)
except ValidationError:
return False
return Truewhat good looks like
- Returns an error rather than a boolean-only success flag
- Handles validation failure immediately
- Keeps the happy path linear after the error checks
Practice
Sketch a Go function that loads configuration, opens a database connection, and returns early if either step fails. Describe where you would log and where you would return the error.
success criteria
- Shows two separate error checks rather than one generic catch-all
- Returns the original error to the caller
- Uses logging only where it adds context rather than hiding the failure
Common mistakes
- Trying to hide repeated error checks inside abstractions too early.
- Using panic where an error return is the normal control path.
- Logging every error at every layer and making failures noisy rather than informative.
takeaways
- ●Once the pattern clicks, Go error handling feels less like ceremony and more like a habit that keeps surprising control flow out of the codebase.
- ●A strong answer points out that the failure path is visible after each operation instead of being deferred to an outer try/except block.
- ●Shows two separate error checks rather than one generic catch-all