skip to content

Retention and Visibility

Whether metadata is dropped at compile time, stored in the artifact, or readable at run time, and why a tool that cannot see it silently does nothing. Interviewers use retention as a depth check.

on this pageshow

questions

4

Attached metadata can be discarded after parsing, kept in the compiled artifact, or readable at run time — what consumes each?

level: middleimportance: must knowfreq 66%

answer

  1. three stops, not two
  2. who is still looking, and when
  3. parse, then artifact, then run time
  4. artifact metadata outlives the compiler
  5. declared on the type, not the consumer

basics

~20 s

Metadata discarded after parsing is visible only inside the compilation that parses the source; metadata stored in the compiled artifact is readable by any tool that reads that file; only run-time-readable metadata answers a query from the running program.

solid answer

~50 s

A survival level is declared once, on the metadata type, and it decides how far past the parser the metadata travels. At the lowest level it exists only while the compiler holds the parsed source, so compile-time checks and generation directives can act on it and nothing of it reaches the output. At the middle level it is written into the artifact's metadata, so anything that reads that file can act on it — a packaging step, a post-build analyzer, and the compiler that builds a *different* module against the artifact — but the running program's metadata queries do not surface it. At the top level it is also exposed to a query on the loaded declaration, which is what a container deciding at startup what to wire needs. The reach is nested upward, and only recompiling the code that carries the marker changes the level.

code

pseudocode · 14 lines
pseudocode
marker Validated(survives = UNTIL_END_OF_PARSE)

@Validated
function transfer(amount) { ... }

// consumer inside the compilation - still holds the parsed declaration
for each declaration in parsedSources:
    if declaration.hasMarker("Validated"):
        check(declaration)                      // runs

// consumer in the deployed artifact - queries the loaded declaration
for each method in loadedType.methods():
    if method.markers().has("Validated"):
        validate(method)                        // never runs: nothing survived

go deeper

for a junior

Recall that attached metadata does not automatically last forever: some of it is gone once the code is compiled, and some of it the running program can ask about.

for a middle

Explain all three levels and name a consumer for each, including the middle one that only a reader of the artifact file can use. Say who declares the level and when it is fixed.

for a senior

Show that you pick the lowest level that reaches the latest consumer, and that you treat a published level as a contract: raising it is free for readers, lowering it quietly removes a reader's input.

for a principal

Frame the level as a platform decision. Defaulting everything to run-time-readable turns every marker into a run-time contract you can never lower without silently disabling somebody's consumer.

## What a survival level actually decides Attaching metadata to a declaration is only half a mechanism. The other half is **when something is still able to read it**. The survival level — commonly called retention — is declared once, on the metadata type itself, and answers one question: how far past the parser does this metadata travel? Three answers exist, and the choice is made by whoever declares the metadata type, not by whoever attaches it and not by whoever wants to read it. It is fixed into every artifact compiled from code carrying the marker, so no configuration on the consumer's side and no run-time switch can reveal metadata that was never written into the artifact. ## The three levels 1. **Discarded after parsing.** The compiler sees the marker while it still holds the parsed declarations, and nothing of it reaches the output file. Compile-time checks, warning suppressions and instructions to a build-time generator live here. 2. **Stored in the compiled artifact, not queryable while running.** The marker is written into the artifact's metadata next to the declaration it decorates. Anything that reads that file can act on it: a packaging or repackaging step, a post-build analyzer, an offline auditor — and, easy to forget, the compiler that builds a *different* module against the published artifact. The running program's own metadata queries do not surface it. 3. **Readable at run time.** The marker is in the artifact *and* the runtime exposes it to a query against the loaded declaration. This is the level a container needs when it decides at startup which declarations to wire, and the only level a call-site check can consult. | Survival level | Who can read it | What it costs | |---|---|---| | Discarded after parsing | Only a consumer inside the same compilation | Nothing in the artifact; no reach past the build | | Stored in the artifact | Any reader of the artifact file, including a downstream compilation | Metadata bytes per annotated declaration | | Readable at run time | All of the above, plus a query from the running program | Those bytes, plus the marker type must ship and resolve at run time | ## Reach is nested, and only in one direction The levels are ordered by how many readers they admit, and every higher level admits everything a lower level admitted: - A consumer running **inside the compilation** holds the source, so it sees markers at all three levels. - A reader of the **artifact file** sees the middle and top levels, and nothing of the lowest. - A **query from the running program** sees only the top level. So "it survives to run time" never means "it stopped being visible to the build" — a common inversion. What is not symmetric is the fix: you can raise the level only by recompiling the code that declares and carries the marker. A consumer downstream of that artifact has no way to raise it. ## Why the middle level is not an oversight Engineers meeting this axis for the first time often ask why a level would put bytes in the artifact that the program cannot read. Two reasons, both ordinary: - **Separate compilation.** A marker that must warn or constrain another team *at their compile time* has to be in the artifact, because the downstream compiler reads your published artifact, not your source. A marker discarded after parsing cannot reach them at all. - **Anything that inspects the artifact offline.** Auditing, licence and dependency tooling, and rewriting steps in the packaging pipeline all read the file rather than run the program. The middle level gives them everything they need while keeping the marker out of the run-time surface. ## Choosing a level The working rule is: **pick the lowest level that reaches the latest consumer that must read it.** In practice that means asking, in order — does anything read this after the build? does anything read it while the program runs? — and stopping at the first yes. - Metadata consumed by a check that runs during compilation: the lowest level. - Metadata that another module's compiler or an offline tool must see: the middle level. - Metadata a container or a call-site check consults while the program runs: the top level. Where runtimes differ is worth knowing but not worth guessing at: some expose the marker's member values through queries in full, some drop a marker whose own type is not resolvable on the run-time path and some surface that as an error instead. The *price* of performing the run-time lookup is a separate concern from whether the metadata is there to be found. The level is also a published contract. Raising it costs no reader anything. Lowering it removes the marker from a phase where someone was already reading it, and because consumers are written as "act where the marker is present", they simply stop acting.

  • A marker must make another team's compiler warn when they call your method. Which is the lowest level that works?
    The middle one — stored in the compiled artifact. Their compiler reads your published artifact, not your source, so a marker discarded after parsing never reaches them. The top level would also work, but it buys reach nobody in this scenario uses.
  • Does a marker readable at run time stop being visible to build-time consumers?
    No. Reach is nested upward: a consumer inside the compilation sees every level, a reader of the artifact file sees the top two, and only a query from the running program is restricted to the top one. Raising the level takes nothing away from anyone.
  • Can a consumer raise the survival level of a marker that a dependency declared?
    Not from outside. The level is fixed when the code carrying the marker is compiled, so changing it means rebuilding that artifact. A consumer can only move itself to a phase where the metadata still exists, or arrange for its own marker to be attached to code it controls.

Think of a note chalked on the scaffolding, a label sealed inside the wall cavity, and a plaque bolted to the facade. The chalk helps the crew and leaves with them, the label is there for anyone who opens the wall, and only the plaque is legible to a passer-by.

saying these in an interview costs you the question

  • Claims every attached marker can be queried by the running program.
  • Says the consumer or the runtime chooses the survival level.
  • Believes a marker in the artifact is automatically visible to run-time queries.
  • Treats source-only metadata as a comment that nothing consumes.
  • Thinks raising the level hides the marker from build-time tools.
open as a page

A marker your build step honours is invisible to the deployed program — how do you pin down which survival level it has?

level: middleimportance: should knowfreq 50%

basics

~20 s

Probe the two boundaries. If the marker is absent from the compiled artifact's metadata, it was discarded after parsing. If it is present but a query on the loaded declaration returns nothing, it lives in the artifact yet is not queryable.

open as a page

A dependency's marker never reaches run time, but your run-time consumer needs it — what are your real options?

level: seniorimportance: should knowfreq 41%

basics

~20 s

You cannot raise another artifact's survival level from outside. The workable moves: read the metadata where it still exists and carry it forward, mark the code you own with your own marker, keep a guarded side mapping, or ask for a rebuild.

open as a page

Your platform publishes markers that dozens of teams attach to their own code — how do you decide each marker's survival level?

level: principalimportance: nice to knowfreq 29%

basics

~20 s

Set 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.

open as a page