Concept · Code design
Guard clauses and the arrow anti-pattern
Check failure conditions first and return early, so the main path stays unindented instead of disappearing into nested ifs.
The arrow anti-pattern is code whose nested conditionals push the real work further and further to the right, until the indentation forms an arrow. Each else sits far from its if, and to understand any line you must hold every enclosing condition in your head.
A guard clause fixes it: test each precondition at the top, handle the failure (return, throw, continue), and let the main path run down the left margin.
Before and after
// Arrow
function shippingLabel(order: Order | null): string {
if (order) {
if (order.status === 'paid') {
if (order.address) {
return formatLabel(order.address)
} else {
throw new Error('missing address')
}
} else {
throw new Error('order not paid')
}
} else {
throw new Error('no order')
}
}
// Guard clauses
function shippingLabel(order: Order | null): string {
if (!order) throw new Error('no order')
if (order.status !== 'paid') throw new Error('order not paid')
if (!order.address) throw new Error('missing address')
return formatLabel(order.address)
}Same behaviour; each rule is now one independent line, and adding a fourth check doesn't deepen anything. In TypeScript, guards also narrow types: after if (!order) throw …, the compiler knows order isn't null.
Why it works
- Readers see the preconditions as a list, then the main logic, with no matching of distant braces.
- Failure handling sits next to the condition that causes it.
- In Go this is simply idiomatic:
if err != nil { return … }is a guard clause, and Go style asks you to keep the normal path unindented.
Related techniques
continueat the top of a loop body instead of wrapping the body in anif.- Extract a deeply nested block into a function with its own guards.
- Replace a long
if/else ifchain on a type with a lookup table or polymorphism.
Some old style guides demanded a "single exit point" per function. That rule comes from languages without automatic cleanup; with defer, finally or RAII, early returns are safe. The clean code lesson shows the same refactoring in Go.