A Go log line reads "get user: get user: query user 42: sql: no rows in result set" — what wrapping mistake caused the repeat?
answer
- count the segments, count the layers
- each layer says what only it knows
- the same words appear twice
- sometimes the right wrap is no wrap
- return err unchanged when you add nothing
basics
~20 sTwo layers wrapped with the same operation text. Each layer should add only the context it alone owns, and a layer with nothing new to say should return the error unchanged rather than prefixing it again.
solid answer
~50 sThe repository wrapped with `get user 42` and the service above wrapped with `get user` too, so the chain repeats itself. Wrapping is not a per-function ritual; it is how a layer contributes the one fact it owns. The repository owns the storage operation and the id, the service owns the use case it was performing, a transport handler usually owns nothing textual at all and should decide what to log and what to render instead. A function that has nothing new to add should just `return err`. The discipline matters beyond tidiness: because a Go error chain flattens into a single string, every extra prefix makes the eventual message longer, harder to read at 3am, and more likely to contain something you would not want a caller to see if any boundary ever renders it.
code
go · 8 lines// repository
return fmt.Errorf("query user %d: %w", id, err)
// service - says the same thing again, so the chain stutters
return fmt.Errorf("get user: %w", err)
// service - adds the use case nobody below it knows
return fmt.Errorf("render profile page: %w", err)go deeper
Be ready to spot the duplicate segments in a chain and say which layer should drop its prefix. Know that returning the error unchanged is a legitimate choice, not laziness.
Explain the mechanics: each wrap prepends text to one flat string, so the number of segments should match the number of layers that actually know something. Give the per-layer split — storage operation and id, use case, and a transport layer that decides rather than prefixes.
Show how you keep chains short in practice: structured log fields instead of English prefixes, review questions for wrap sites, and the connection between chain length and how auditable the message is before anyone renders it.
Own the convention across services so operators read one shape everywhere. Decide whether the team wraps by layer or logs by field, and be honest that a blanket wrap-everywhere rule buys consistency at the cost of readability and disclosure surface.
## What the repeated prefix is telling you `get user: get user: query user 42: sql: no rows in result set` is four segments, and two of them say the same thing. That happens when wrapping becomes reflexive — every function on the path adds a prefix because the codebase's convention is "always wrap" rather than "add what you know". ## The rule: one layer, one contribution Each `fmt.Errorf` prefix should answer a question the layers above cannot answer for themselves. - **Repository / storage layer** — knows the concrete operation and the key: `query user 42`. It does not know why anyone wanted the user. - **Service / domain layer** — knows the use case: `render profile page`, `process signup`, `reconcile invoice`. It does not know which statement ran. - **Transport layer (an HTTP handler, a queue consumer)** — usually knows nothing new in words. Its job is a decision, not a prefix: what to log, and what the caller is allowed to see. Adding `handle GET /users/{id}` is normally noise, because the request is already a structured log field. Read top to bottom, a well-formed chain is a sentence that narrows: *why → what → which → the raw cause.* A chain with duplicates is a sentence that stutters. ## When not to wrap at all A plain `return err` is the right answer more often than people expect: - A thin pass-through that only forwards a call and has no id, no operation name of its own, and no decision to make. - A function whose only failure mode is the callee's, and where the callee's message already contains everything the caller needs. - A helper whose name is already the operation and whose call site is unambiguous. Wrapping such a function costs a segment and buys nothing. This is also the reason "wrap everywhere" linting rules feel wrong in practice: the number of wraps should equal the number of layers that actually know something, not the number of frames on the stack. ## Why Go pushes people into over-wrapping Because Go errors carry no stack trace, wrapping is the only way a reader can reconstruct the path. So teams over-correct and turn every frame into a text segment, effectively hand-rolling a stack trace out of strings. It is a poor stack trace — no line numbers, no ordering guarantee, and it costs message length — and it drives two secondary problems: 1. **Unreadable operator output.** A five-segment message with two repeats is skimmed rather than read, and the one segment that mattered (the id) gets lost in it. 2. **A wider disclosure surface.** The more text layers pile in, the higher the chance that one of them added a query, a host name or a path — and because the chain is a single flat string, that fragment reaches wherever the chain reaches. If you genuinely need per-frame provenance, structured logging at the boundary gives it to you properly: log the error once with named fields (`op`, `id`, `req_id`) rather than encoding those fields into English. ## How to fix the example Either the service adds the use case it uniquely knows: ```go return fmt.Errorf("render profile page: %w", err) ``` or, if it is a pass-through, it stops wrapping: ```go if err != nil { return err } ``` Both produce a chain that reads once: `render profile page: query user 42: sql: no rows in result set`. ## A second smell in the same family Segments that restate the callee: `scan row: scan: ...`. The wrapped error already supplies its own words, so your prefix should never be a synonym for the function you just called. Same cause, same fix — say what *you* know, not what the thing below you is about to say. ## What to check in review - Does each prefix name an operation at that layer's own level of abstraction? - Is there exactly one segment per layer that has something to say? - Is the id present exactly once, at the layer that had it? - Would this message be readable if it were the only line in a log? A chain that survives those four questions is short, searchable, and small enough that you can see at a glance whether anything in it is unsafe to render.
- When should a Go function return err unchanged instead of wrapping it?When it has nothing to contribute: a thin pass-through with no identifier of its own, no operation name at a different level of abstraction, and no decision to make. Wrapping there costs a message segment and buys no information, which is how chains grow to five segments that say three things.
- Isn't a long chain a reasonable substitute for a stack trace?It is a poor one. There are no line numbers, no frames that did not wrap, and the whole thing is a single flat string you cannot filter. If you want per-frame provenance, log once at the boundary with named structured fields such as op, id and request id, which are queryable in a way English prefixes never are.
- How does an over-long chain make a leak more likely?Every extra segment is another chance that some layer pasted in a query, a host name or a file path, and the chain is one flat string with no marking of which part is safe. Short chains are auditable at a glance; five-segment chains are not, so unsafe fragments survive review.
saying these in an interview costs you the question
- Wraps in every function because the convention says always wrap
- Prefixes with a synonym of the function just called
- Adds the same identifier again at two different layers
- Uses wrap segments as a hand-rolled stack trace
- Claims a bare return err is always a style violation