In Go, why is the success path never put inside the `else` of an `if err != nil` check?
answer
- read the left column top to bottom
- the failure branch is the short one
- one check per call, and they stack
- no else after a return
basics
~20 sIdiomatic Go handles the failure inside the if and returns, leaving the success path unindented below it. Because Go checks an error after nearly every call, putting each success in an else pushes the real work rightward.
solid answer
~50 sGo has no exceptions, so a function of any size carries an `if err != nil` after nearly every call. The convention is to make that block the short one: handle or return the failure inside it, then continue at the same indentation as before. Written the other way, with the work inside `else`, each check costs a level of nesting, and a handler with four calls buries its one interesting line four tabs deep. Keeping the success path at the left margin is what Go reviewers mean by line of sight: you read the left column top to bottom and see what the function does, while the failure branches sit to the right and are skimmed. It also keeps each branch to one or two statements, which is what makes them individually reachable in tests.
code
go · 13 linesfunc (s *server) handleGetUser(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err == nil {
u, err := s.loadUser(r.Context(), id)
if err == nil {
writeJSON(w, u)
} else {
http.Error(w, "not found", http.StatusNotFound)
}
} else {
http.Error(w, "bad id", http.StatusBadRequest)
}
}go deeper
Be ready to write both shapes side by side and say which one Go reviewers expect: the failure handled and returned inside the if, the success continuing below it at the same indentation.
Explain why the cost compounds - one check per call, each else a new block - and show the mechanical fix: invert the condition, return early, then lift the middle of the function into a helper.
Show what the shape buys operationally. Shallow branches are individually reachable in tests, legible in a per-statement coverage report, and give a reviewer a chance of spotting a path nobody has exercised.
Own it as a review standard rather than a personal preference: say what you would flag routinely, what you would not flag (arbitrary function-length limits), and why one shape the whole team reads identically is worth more than any single rewrite.
## The shape being described Go reports failure by returning it. There is no `try`/`catch` and no stack unwinding on an ordinary error, so a function that makes four calls that can fail contains four checks. That volume is the whole reason this convention exists: a rule about indentation that costs nothing in a language with one error site per function becomes structural in a language with one per call. The two shapes are: ``` // success inside else - the work drifts right if err == nil { ... do the work ... } else { return err } // early return - the work stays at the left margin if err != nil { return err } ... do the work ... ``` With one check the difference looks like taste. With four, the first shape ends with the interesting statement indented four levels and its matching closing braces spread over the bottom of the function, while the second keeps every statement of the normal path in a single column. ## Line of sight The phrase Go reviewers use is *line of sight*: a reader should be able to run their eye down the leftmost column of a function and read the story of what it does, with the exceptional cases parked to the right where they can be skipped on a first pass. That works only if the normal path never moves right. Once one call's success is nested, everything after it inherits that indentation, and the reader has to hold in their head which condition they are currently inside. The corollary is a small rule with a big effect: **no `else` after a branch that returns**. If the `if` body ends in `return`, `break`, `continue` or a call that cannot come back, the `else` is redundant - the code after the `if` is already the else. Deleting it un-indents everything below. ## What this is not about Two things are commonly confused with it. First, **gofmt does not do this for you**. gofmt is a formatter: it fixes whitespace, alignment and brace placement, and it will happily format a five-level nest into beautifully indented five-level nesting. Restructuring is a human edit. Second, **an `else if` chain does not add indentation**. In gofmt'd Go, `} else if cond {` sits on the closing brace line, so every condition in a chain of ten is at the same column as the first `if`. The cost this convention is about comes from nesting a block inside a block, not from chaining. That matters, because "replace the chain with a switch" is often proposed as a fix for depth and does not reduce depth at all. ## Guard clauses The same shape at the top of a function is usually called a guard clause: validate the inputs first, one condition per `if`, each returning immediately. ``` if id == "" { return nil, errMissingID } if limit < 0 { return nil, errBadLimit } // from here down, everything is known good ``` By the time the body starts, the preconditions hold, so the body needs no defensive nesting. Each guard is one reachable line with one input that triggers it. ## When the failure branch is long Sometimes handling the error genuinely takes several lines - write a response, increment a counter, clean something up. The convention still holds, because the fix is to move those lines into a small function and call it: ``` if err != nil { writeErr(w, err) return } ``` The rule is about where the *success* path lives, not about a maximum length for the failure block. Keeping the failure branch to a call plus a `return` is simply how you keep the left column intact. ## Why it is more than style A flat function has a testing property a nested one does not. Each branch is reached by one condition, so one input exercises it, and a per-statement coverage report shows plainly which branches nothing has run. In a five-level nest, reaching the innermost branch requires satisfying four outer conditions, so tests carry setup unrelated to what they check - and branches that are hard to reach in a test are usually branches nobody has read either. That is the practical argument to make in review when someone treats the shape as a preference: the nesting is what let an unread branch survive into production. ## What a reviewer says The useful review comment is mechanical rather than aesthetic: "invert this condition and return; the rest of the function moves left one level." It is a safe, local edit, it is obvious in the diff, and it is the same edit every time.
- Does an `else if` chain in gofmt'd Go actually indent one level further with each condition?No. gofmt keeps `} else if cond {` on the closing brace line, so every condition in the chain sits at the same column as the first `if`. The cost this convention is about comes from putting a block inside a block, or putting the work inside `else`, not from chaining conditions. That is why the fix for a deep function is early returns and extraction rather than rewriting the chain.
- What if handling the failure genuinely takes ten lines - does the shape still hold?Yes. Move the ten lines into a small function and call it from inside the `if`, then return. The convention is about where the success path lives, not about a length limit on the failure block. Keeping that branch to a call plus a `return` is what preserves the left column, and it usually improves the failure handling too by giving it a name.
- What does the guard-clause shape at the top of a function look like?One condition per `if`, each returning immediately: empty id, negative limit, nil dependency. By the time the body starts, everything is known good, so the body needs no defensive nesting. Each guard is a single reachable statement with a single input that triggers it, which makes both the test and the coverage report trivial to read.
It reads like a checklist with exits: each line either sends you out the door or lets you continue down the page. Nesting turns the same checklist into a set of nested boxes you must open in order.
saying these in an interview costs you the question
- Claims the Go compiler rejects an else after a return
- Thinks gofmt restructures nesting rather than only formatting it
- Says an else-if chain adds one indentation level per condition
- Puts the entire function body inside if err == nil
- Treats nesting as pure taste with no testing consequence