skip to content

Your team duplicates a validation pipeline per result wrapper — when is making it wrapper-generic the wrong call?

level: principalimportance: should knowfreq 32%

answer

  1. count the wrappers, then decide
  2. native support or an encoding?
  3. normalising at the boundary is cheaper
  4. the tax lands on every reader
  5. prefer the reversible rung

basics

~20 s

When the wrapper count is small and stable, when the platform forces an encoding whose diagnostics and inference costs land on every reader, or when normalising at the boundary would have removed the duplication for a fraction of the price.

solid answer

~40 s

Wrapper-generic code is a real abstraction with a real tax, and the tax is paid by everyone who reads the path afterwards. Ask four questions. **How many wrappers, and is the number growing?** Two stable copies are cheap to keep in step. **Is the parameter supported natively, or does it need an encoding?** Native support costs a line of signature; an encoding costs a second vocabulary, weaker inference and error messages about markers rather than about the caller's code. **Would normalising at the boundary do?** If one wrapper dominates and the rest live at the edges, convert and keep the core concrete. **Who maintains it?** An abstraction only one person can extend is a bus-factor problem dressed as elegance. Adopt when the wrapper count keeps growing and the platform supports it directly.

go deeper

for a junior

Notice the duplication and say so, but let someone with more context weigh the abstraction. Recording that three copies exist is already a useful contribution.

for a middle

Be able to cost both sides concretely: how many places a new stage must be edited today, and what the abstraction would add to every signature on the path.

for a senior

Bring evidence rather than preference — the drift defects that actually happened, the conversion the boundary would need, what the compiler's messages look like under an encoding.

for a principal

Decide and make the decision inheritable: name the rung, write down the trigger that moves the team up it, and say which choice you could climb back down from if you are wrong.

## The bet you are actually placing Making a pipeline wrapper-generic is not a refactor confined to one file. The extra parameter appears in the signature of everything on that path, the constraint bundle appears with it, and every caller and every test touches the new vocabulary. So the decision is not "is duplication bad" — it always is, a little — but "is the abstraction's permanent cost smaller than the duplication's recurring cost, for this codebase, on this platform, with this team". ## Signals it pays - **The wrapper count is growing.** Three today and a fourth landing next quarter is a different situation from two that have been stable for two years. - **The parameter is supported natively.** Where the type system genuinely admits a parameter that itself takes a parameter, the abstraction costs about one line of signature and the compiler's messages still name the caller's wrapper. - **The duplicated bodies are non-trivial and keep drifting.** If defects have actually appeared in one copy and not the others, the duplication is charging you already. - **The stage set changes often.** Each new stage multiplied by N copies is the recurring cost that the abstraction eliminates. - **The wrappers are genuinely peers.** None is a special case the others could be converted into. ## Signals it does not - **Two wrappers, stable.** Two copies reviewed side by side are easier to read than one encoded body, and a test can pin them to the same behaviour. - **The platform forces the marker-and-lookup encoding.** Then the price is a second vocabulary, degraded inference, and diagnostics that talk about the encoding instead of the code. That price is paid by every future reader, including the ones who never touch the abstraction deliberately. - **One wrapper already dominates the interior.** Converting the others at the boundary is cheaper and more legible; the loss is bounded and visible at a named conversion. - **Nobody else on the team can extend it.** An abstraction with a single maintainer is a queue, not leverage. - **The motivation is the technique.** "We wanted to learn it" is a legitimate reason to prototype and an illegitimate reason to ship it on the critical path. ## A ladder, cheapest rung first 1. **Duplicate and wait.** Two copies, a shared test suite asserting identical behaviour, and a note recording why. Revisit when a third wrapper appears. 2. **Normalise at the boundary.** Choose the most expressive wrapper, convert the others at the edges, keep one concrete pipeline. Name what the conversion discards. 3. **Generate the copies.** One template, a build step. Good when the bodies are large and mechanical, bad when generated code has to be debugged by people who did not write the template. 4. **Go wrapper-generic natively.** The right destination where the platform supports the parameter shape directly. 5. **Encode it.** Markers, lookup, lift and lower. Justified when the abstraction is the product — a library other teams depend on — and rarely when it is internal glue. The ladder matters more than any single verdict: a lead who can say which rung the codebase is on and what event moves it up is making a decision, while one who only argues for or against the abstraction is expressing a taste. ## What you own once you say yes | Decision | What to check before committing | Evidence a year later | |---|---|---| | Adopt wrapper-generic | Can two other engineers add a stage unaided? | Stages added by people who did not build it | | Encode rather than duplicate | Do error messages still point at the caller? | Time spent decoding diagnostics in review | | Normalise instead | Is what the conversion drops acceptable and named? | Bug reports about information lost at the edge | | Do nothing yet | Is drift being caught by a shared test suite? | Behaviour differences found in production | ## Reversibility is the tiebreaker Normalising at the boundary is easy to undo: the conversions are named functions at the edges and the interior is ordinary concrete code. An encoding is hard to undo, because callers adapt to it and the vocabulary spreads outward from the pipeline. When the evidence is genuinely balanced, prefer the rung you can climb back down from, and set the trigger — a fourth wrapper, a second drift defect — that moves you up.

  • What trigger would you write down today that moves the team up the ladder later?
    Something countable and checkable: a fourth wrapper entering the interior, or a second defect found in one copy but not the others. Write it next to the duplicated code with the date and the reasoning, so the next engineer inherits the decision rather than re-litigating it or quietly adding a fifth copy.
  • How would you evaluate the abstraction a year after adopting it?
    By who touches it. If engineers who did not build it add stages unaided, it paid. If every change queues behind one person, or reviews are spent decoding error messages about the encoding rather than the logic, it did not — and the honest move is to normalise the edge wrappers and take the interior back to concrete code.

saying these in an interview costs you the question

  • Argues the abstraction is always right because duplication is always wrong.
  • Ignores that an encoding taxes readers who never use it deliberately.
  • Treats two stable copies as the same problem as six growing ones.
  • Picks the technique because the team wants to learn it.
  • Never considers normalising the edge wrappers at the boundary.
  • Has no trigger for revisiting the decision later.