How would you set a codebase-wide convention for handler return types so response mapping stays predictable across hundreds of endpoints?
answer
- the convention is the contract
- terse inference versus visible ceremony
- default plus a stated exception
- error paths always explicit
- a framework default can change under you
basics
~20 sPick a default and make deviation visible: plain returns where only the body varies, a wrapper wherever status or headers do, error paths always explicit. Then enforce it mechanically and re-check on every framework upgrade.
solid answer
~50 sThe choice is between leaning on inference everywhere and wrapping everything, and both extremes cost something. **All-inferred** gives terse handlers but leaves the status of every endpoint undocumented in code, so the response contract lives only in the framework's defaults. **All-wrapped** is uniform and greppable but adds ceremony to the majority of endpoints where only the body varies. The convention I would set is a **default plus a stated exception**: return the plain value for success-with-body, return a wrapper anywhere status or headers vary, and require a wrapper on **every error path** so a failure can never inherit the success default. Then make it enforceable — a shared result type, an architecture test or lint rule, and a written page naming the inference rules you depend on. Finally, treat framework upgrades as contract events, because a changed default silently rewrites every inferred endpoint.
go deeper
Follow the codebase convention rather than inventing one, and when unsure make the status explicit — an extra wrapper is cheap, a silent success on an error path is not.
Be able to argue both poles honestly: terse inference hides the contract, uniform wrapping adds ceremony nobody reads. Know which endpoints genuinely need to be explicit.
Own the enforcement: shared result types, a build rule for asymmetric branches, contract tests on status and headers, and a read of release notes for mapping changes.
Frame the convention as the response contract itself, give it an owner, and remember that inferred behaviour is a decision delegated to a dependency that can change it without a diff in your repository.
## Why this is a convention problem, not a style preference At ten endpoints, how handlers return is taste. At several hundred, across teams, the return convention *is* the response contract: every endpoint whose status was inferred asserts something no one wrote down, and every inconsistency becomes a branch in some client. The question an interviewer is really asking is whether you can hold a boundary that nobody sees until it breaks. ## The two poles and what each costs | | All inferred | All wrapped | |---|---|---| | Handler code | terse; the domain value is the whole handler | ceremony on every path, including trivial ones | | Where the contract lives | in framework defaults, off the page | in the code, greppable | | Error paths | easy to get wrong; a bare return inherits success | symmetric by construction | | Generated API docs | derived from the return type, so statuses are guesses | derived from stated statuses | | Framework upgrade | a changed default rewrites every endpoint at once | unaffected where statuses are stated | | Review load | reviewers must remember the defaults | shape is visible in the diff | Neither pole is obviously right, which is why this is a judgment question. The failure mode of the first is invisible drift; of the second, ceremony that reviewers stop reading — and a wrapper nobody reads is as good as no wrapper. ## The convention I would set 1. **Plain returns are the default** for success-with-body. Most endpoints are this, and inference is a real productivity gain. 2. **A wrapper is mandatory where status or headers vary** — anything that needs a non-default status, a result-derived header, or a deliberately empty body with a chosen status. 3. **Every error path is explicit.** This is the rule that pays for itself. Asymmetric branches are how failures ship as successes. 4. **Cross-cutting headers live in one shared stage**, never copied into wrappers, so they cannot go missing from the branch nobody updated. 5. **One shared result type per service family**, so wrappers look identical everywhere and a reviewer recognises the shape instantly. 6. **Page-oriented and data-oriented routes stay in separate groups**, so a string returned in one group never means what it means in the other. ## Making it hold A convention that lives only in a document decays. Three mechanisms, cheapest first: - **A written page naming the inference rules you rely on** — what an empty return maps to, what an absent value maps to, what a bare string means in each route group. Without this, nobody can tell a deliberate default from an oversight. - **Mechanical enforcement.** An architecture or lint rule that fails the build when a handler in the error-handling path returns a bare value, or when both a response mutation and a wrapper appear in one handler. Rules people cannot violate by accident are worth more than rules they are reminded of. - **Contract tests at the edge.** A small suite that asserts status and key headers per endpoint catches exactly the class of drift the convention is meant to prevent, and it keeps working when the framework's defaults move. ## Treat upgrades as contract events The deepest risk of inference is that the default is the framework's, not yours. If a major version changes what an empty return or an absent value maps to, every endpoint that relied on it changes behaviour **at once**, with no diff in your repository. So: pin the endpoints whose status matters with explicit wrappers, keep the contract tests, and read the release notes for mapping changes specifically. A convention that survives an upgrade is worth more than one that is merely tidy. ## Organizational angle If several teams share a surface, the convention has to be discoverable at the moment of writing — a shared module of return helpers, a template service, a review checklist of three lines rather than thirty. And it needs an owner: conventions without one become the union of every team's habits, which is the outcome the convention existed to prevent. ## What a good answer sounds like Name both poles and their real costs, commit to a default with a stated exception rather than pretending one pole is obviously correct, and then spend most of your answer on **how it is enforced** — shared types, mechanical rules, contract tests, upgrade discipline. Mentioning that a framework default is somebody else's decision that can change under you is the point that marks a principal-level answer.
- Which single rule would you enforce first if you could only enforce one?Every error path returns an explicit status. Asymmetric branches are the highest-frequency, highest-damage defect in return mapping: the failure ships with the success default, clients treat it as fine and alerting stays quiet. It is also the easiest rule to check mechanically, because it is a property of one handler.
- How does the return convention affect generated API documentation?Generators read what the code states. Where a status is inferred they can only publish an assumed default, so documented statuses drift from real ones exactly on the endpoints that deviate. Stating status in the return type makes the generated document a description rather than a guess — a strong argument for wrapping wherever the status is not the default.
- What makes a framework upgrade risky for a codebase that leans on inference?The defaults belong to the framework. If a version changes what an empty return or an absent value maps to, every endpoint relying on it changes at once with no diff on your side. Explicit wrappers on endpoints whose status matters, plus edge contract tests asserting status and headers, turn that into a caught test failure.
saying these in an interview costs you the question
- Declares one pole universally correct without naming its cost
- Relies on a documentation page with no mechanical enforcement
- Assumes framework mapping defaults are stable across major versions
- Lets error paths follow whatever convention each team prefers
- Copies cross-cutting headers into every wrapper by hand