skip to content

Teams using your marker-driven processor want its behaviour on their own types rather than on a companion: what do you standardise?

level: principalimportance: should knowfreq 30%

answer

  1. price the substitute, not the emit
  2. who pays: the call sites
  3. the inheritance slot is finite
  4. leaving the model loses diagnostics
  5. a leaked name becomes public API

basics

~20 s

Pick the emission shape by what consumers pay, not what is easiest to emit: a companion leaks a generated name into call sites, a generated supertype hides it but spends the inheritance slot, and rewriting compiled output costs source-anchored diagnostics.

solid answer

~50 s

The request is for an edit the add-only model cannot give, so the decision is which substitute you make everyone live with. A **companion type** is the safest: order-independent, reproducible, and nothing surprising appears on the author's type — but every call site names a type the author never wrote. A **generated supertype** hides the generated name behind ordinary inheritance and costs the type's inheritance slot where a language has only one, which collides with any other tool making the same bet. A **language merge mechanism** is the cleanest where it exists, and unusable as a standard if your consumers span languages that lack it. **Rewriting compiled output** delivers what was asked and leaves the processing model: no source-anchored diagnostics, a build step each team must install, and tooling that can no longer be told the truth by reading the source. Standardise one, state the trade in writing, and make the generated surface predictable enough that the leak is cheap.

go deeper

for a junior

Recall that the tool decides where generated behaviour lands, and that a marker on your type usually gets you something next to it rather than inside it.

for a middle

Explain the substitutes available — companion, generated supertype, hand-written forwarder — and what each one changes for the code that calls it.

for a senior

Price the choice for a real codebase: how many call sites the generated name reaches, whether the inheritance slot is free, and what the team loses if diagnostics stop pointing at their source.

for a principal

Own the decision across teams: pick one shape, write down the stability promise for the generated surface, require a diagnostic on every decline, and keep an opt-out for the teams the standard does not fit.

## Why this is a judgment call and not a lookup The underlying constraint is fixed: a build-time processor adds files and does not amend the declarations it was given. So "put the behaviour on my type" cannot be granted as asked. What a platform owner decides is which **substitute** becomes the standard for every team on the shared build — and that decision is paid by consumers, for years, in code nobody re-reads. The failure mode to avoid is choosing by what is easiest to emit. Emitting a companion is the least work for the tool author and pushes the cost onto every call site in the organisation. ## The four shapes, priced | shape | what the author writes | what callers see | what it costs at scale | |---|---|---|---| | companion type beside the marked one | only the marker | a generated name at each call site | the leak multiplies across every consumer; renames are wide | | generated supertype the type extends | marker plus one declaration | nothing unusual | spends the single inheritance slot where one exists; two tools cannot both do it | | generated interface plus hand-written forwarder | marker plus a line per type | nothing unusual | the forwarder is hand-written, so it drifts from the generated signature | | rewriting the compiled output | only the marker | exactly what they asked for | leaves the model: no positioned diagnostics, an extra step per build, source no longer tells the truth | A fifth option exists where the language itself offers a merge mechanism the author opts into. It is the best answer where it applies and the worst standard where your consumers span languages that do not have it, because the standard then has an exception for half the organisation. ## The questions that decide it 1. **How many call sites?** A generated name leaking into ten call sites is a non-issue; leaking into every service's request path is an organisational tax and argues for the supertype or the merge mechanism. 2. **Is the inheritance slot already spoken for?** If another tool, a framework base or the team's own hierarchy already claims it, the supertype is not available and pretending otherwise produces a standard that fails on first contact. 3. **Who owns the extra build step?** Compiled-output rewriting is not free-standing: someone maintains it, someone debugs it when a build machine runs a different toolchain, and someone explains to an author why the error message mentions no file of theirs. 4. **What do the tools read?** Editors, navigation, review tooling and static analysis all read source. A mechanism that makes the compiled result differ from what the source says degrades all of them at once, which is a cost that never appears in the benchmark that justified the change. 5. **Can a team opt out?** A standard nobody can decline becomes a hostage situation the first time it is wrong for someone. Every shape above can be made declinable except the one that rewrites output for the whole build. ## What to standardise beyond the shape Having picked a shape, the durable part of the decision is the contract around it: - a **deterministic name** derived from the marked declaration, so the leaked name is guessable rather than looked up; - a rule that the processor **reports a diagnostic on the marked declaration** whenever it declines to generate, so the absence of generated code is never silent; - a statement of what the processor **never emits** — in particular, output carrying its own trigger marker, which would make the rounds loop spin; - the **stability promise**: whether the generated surface is public API that consumers may depend on, or an implementation detail the tool may reshape. The last is where organisations get hurt. A generated name that leaks into thousands of call sites has become public API whether or not anyone intended it, and the tool author discovers this the first time they want to change it. ## How to answer this in an interview A strong answer refuses the premise cleanly — the edit is not available — then prices at least two substitutes against real consumer costs, names the inheritance slot and the diagnostics loss as the two concrete prices, and ends with the contract that makes whichever shape you picked survivable: predictable names, a diagnostic on every decline, and an explicit statement of what consumers are allowed to depend on. A weak answer picks the shape that is easiest to generate and treats the consequences as somebody else's problem.

  • What makes a generated supertype a risky organisational standard?
    It spends a resource that is finite in some languages and already claimed in many codebases: the single inheritance slot. Two tools cannot both take it, a framework base may already hold it, and teams with their own hierarchy have to choose. The standard then needs an exception, which is where standards start to rot.
  • Why does rewriting the compiled output cost more than the extra build step suggests?
    Because everything that reads source stops telling the truth: editors, navigation, review and analysis all see a program without the added behaviour. Diagnostics also stop being anchored to the author's declaration, so a mistake in a marker surfaces as a failure in a step that names no file of theirs.
  • If a companion's generated name leaks into thousands of call sites, what have you actually published?
    Public API. Whatever the documentation says about it being an implementation detail, consumers now depend on that name, and any change to it is a coordinated migration. The mitigation is to decide that deliberately up front — either promise the surface and version it, or hide it behind something hand-written and narrow.

saying these in an interview costs you the question

  • Picks the shape that is easiest to emit, not easiest to consume
  • Standardises a generated supertype without counting the inheritance slot
  • Treats rewriting compiled output as a free variant of processing
  • Promises in-place edits the add-only model cannot deliver
  • Assumes every team's build can host the same processor
  • Calls a widely referenced generated name an implementation detail