Your platform publishes markers that dozens of teams attach to their own code — how do you decide each marker's survival level?
answer
- reach is a commitment, not a convenience
- a named consumer per step up
- publish the level with the marker
- raising rebuilds, lowering breaks silently
- the permissive default is the irreversible one
basics
~20 sSet each marker at the lowest level that reaches the latest consumer it genuinely has, and publish that level as part of the marker's contract. A blanket run-time-readable default is the expensive choice: it makes every marker a run-time commitment you can never lower quietly.
solid answer
~40 sDecide per marker, not by policy default. For each one, name the latest phase where something must read it — a check during compilation, a downstream compiler or offline tool reading the artifact, or a consumer querying the running program — and set the level to the lowest that reaches it. Publish the level alongside the marker, because it is a contract: raising it later is free for readers, lowering it removes a reader's input with no error anywhere. Defaulting everything to run-time-readable looks harmless and is not: it commits every marker's type to shipping and resolving on the run-time path, it puts metadata in every artifact that attaches one, and it invites run-time consumers you never intended, whose existence then blocks you from lowering the level again.
go deeper
The point to take away is that a marker's survival level is part of what a platform promises, not a detail. Check it before assuming your code can read the marker while running.
Explain the mechanism behind the policy: which reader each level admits, and why raising a level adds readers while lowering one removes them without any error.
Argue from named consumers. Set the lowest level that reaches a real one, publish it, and treat a proposed change to it with the review a published interface change gets.
Own the asymmetry explicitly: the permissive default is the one you cannot reverse, because unplanned consumers accumulate against it and their failure mode is silence.
## Why this is a decision and not a default When one team declares a marker for its own use, the level is a local choice. When a platform publishes markers that many teams attach to code the platform does not own, the level becomes something else: **a commitment about which phases may consume it**, made once and then very hard to walk back. The failure mode is not that a level is wrong on the day it is chosen; it is that the easiest default — make everything readable at run time, it can only help — turns out to be irreversible. ## Decide per marker, from the latest honest consumer For each published marker, write down what actually reads it and when. The level is then the lowest one that reaches the latest of them: - Read only by a check that runs during compilation, and by nothing afterwards → **discarded after parsing**. - Read by a downstream compilation, or by tooling that inspects shipped artifacts offline → **stored in the artifact**. - Read by a consumer that queries the running program → **readable at run time**. The discipline is to require a named consumer for every step up. "Someone might want it later" is not a consumer; it is how a platform ends up with the blanket default it cannot reverse. A level can always be raised, and raising it takes visibility away from nobody. ## What the permissive default really costs A run-time-readable default is not free, and the costs are of different kinds: - **Artifact metadata everywhere.** Every declaration any team marks carries the record, in every artifact they publish. Individually negligible, collectively a line item at platform scale. - **A run-time dependency on the marker type.** The type itself must ship on the run-time path and be resolvable there, which drags a platform artifact into every deployment that would otherwise only have needed it while building. - **Reduced reach for whole-artifact analysis.** Metadata that only matters during the build, kept queryable at run time, enlarges the surface that tooling must treat as live rather than as build scaffolding. - **Consumers you did not plan for.** The decisive one. Once the marker is queryable, someone writes a consumer against it, and now the level is load-bearing for a use you never designed. - **Irreversibility.** Because that consumer exists, lowering the level would silently stop its work. Lowering is the one direction of this change that fails without a signal. The opposite mistake is real too. Levels set too low force every consumer into the build: a team that wanted a run-time decision cannot have one, and the fix is a rebuild of everything carrying the marker on the platform's schedule rather than theirs. ## Treat the level as published API Three practices make this manageable: 1. **Publish the level with the marker.** Documentation that lists a marker's meaning and its targets but not how long it survives leaves every consumer to discover the answer by deploying. 2. **Review a level change like an interface change.** Raising it needs a named consumer and an acknowledgement that a dormant run-time consumer may wake up. Lowering it needs evidence that nothing reads the marker in the phase you are removing — and there is no compiler that will tell you. 3. **Keep the reversible direction open.** Start markers at the lowest level that has a consumer today. Raising later costs a rebuild of the carrying code; lowering later costs a silent outage in somebody else's system. | Bias | What it buys | What it costs | |---|---|---| | Lowest level with a real consumer | Reversible, minimal run-time surface | Rebuilds when a new consumer appears later | | Run-time-readable by default | Nobody is ever blocked | Metadata everywhere, unplanned consumers, irreversible | ## The judgment a lead is actually asked for There is no universally right answer here, and an interviewer asking this wants to hear the trade-off named rather than a rule recited. The defensible position is usually: default low, require a named consumer to go higher, publish the level, and accept the occasional rebuild as the price of keeping the cheap direction of change available. A platform that defaults high is optimising for never being asked for a rebuild, and pays for it by never being able to take a run-time commitment back. Note too that the *price of performing* a run-time metadata lookup is a separate question from whether the metadata is reachable at all — this decision is about reach, and it is the one that cannot be undone later.
- A team asks you to raise a published marker to run-time-readable. What do you ask for first?The consumer. A named thing that must query it in a running program, and a reason it cannot read the metadata during the build instead. Then check that the marker's type can ship on the run-time path, and warn that any existing run-time consumer of that marker may start matching declarations it previously skipped.
- How would you ever lower a published marker's level safely?By finding every consumer in the phase you are removing, since nothing will fail to tell you. Announce it as a breaking change, give teams a release to move their consumer earlier, and publish the marker at the lower level only once the reads in that phase are gone.
- Does starting every marker at the lowest level risk locking teams out?It risks delaying them, not locking them out: raising a level is always available and costs a rebuild of the code carrying the marker. That is a real cost on a large estate, which is why the decision is per marker and why a plausible consumer today justifies starting higher.
saying these in an interview costs you the question
- Says making every marker run-time-readable costs nothing.
- Treats the survival level as an implementation detail rather than published contract.
- Assumes a level can be lowered later once nobody objects.
- Sets levels by guessing at future consumers rather than named ones.
- Forgets the marker's own type must ship on the run-time path.