Explicit errors: from thrown failures to visible control flow
Translate JavaScript and Node error instincts into Go's explicit error-return style.
by the end of this lesson you can
- →Returns an error explicitly
- →Handles validation failure without hiding control flow
- →Keeps the happy path linear after the checks
Overview
JavaScript developers are used to thrown errors, rejected Promises, and framework-specific error shapes. Go puts failure directly into return values and expects you to deal with it close to the call site.
In JavaScript/Node, you often
let failures reject Promises or throw errors that higher levels handle later.
In Go, the common pattern is
to return `(value, err)` and make the failure path visible immediately.
why this difference matters
This changes how you read code: failure stops being implicit background control flow and becomes part of the ordinary path through the function.
JavaScript/Node
try {
const user = await loadUser(id);
} catch (error) {
console.error(error);
}Go
user, err := loadUser(id)
if err != nil {
log.Println(err)
return err
}Deeper comparison
JavaScript/Node version
async function renderDashboard(id) {
try {
const profile = await loadProfile(id);
const stats = await loadStats(id);
return buildDashboard(profile, stats);
} catch (error) {
logger.warn(error);
return null;
}
}Go version
func renderDashboard(id int) (*Dashboard, error) {
profile, err := loadProfile(id)
if err != nil {
log.Println(err)
return nil, err
}
stats, err := loadStats(id)
if err != nil {
return nil, err
}
dashboard := buildDashboard(profile, stats)
return &dashboard, nil
}Reflect
What changes when you stop assuming failures travel outward invisibly and instead make them part of each function's visible flow?
what a strong answer notices
A strong answer mentions local clarity, easier debugging of unhappy paths, and fewer hidden control-flow jumps.
Rewrite
Rewrite this JavaScript error flow into Go using explicit error returns.
Rewrite this JavaScript/Node
function saveUser(user) {
if (!user.name) {
throw new Error("missing name");
}
return repository.save(user);
}what good looks like
- Returns an error explicitly
- Handles validation failure without hiding control flow
- Keeps the happy path linear after the checks
Practice
Design a Go function that loads config, opens a connection, and returns early if either step fails.
success criteria
- Uses separate error checks for separate operations
- Returns the original error clearly
- Avoids trying to recreate Promise-style failure flow
Common mistakes
- Trying to hide all repeated error checks immediately.
- Expecting thrown-style control flow instead of visible returns.
- Logging at every layer because JavaScript frameworks often centralize error shapes differently.
takeaways
- ●This changes how you read code: failure stops being implicit background control flow and becomes part of the ordinary path through the function.
- ●A strong answer mentions local clarity, easier debugging of unhappy paths, and fewer hidden control-flow jumps.
- ●Uses separate error checks for separate operations