Metaprogramming
Code that inspects, emits or rewrites other code: run-time inspection, attached metadata, build-time emission, expansion and interception. Interviewers use it to turn framework magic into mechanism.
on this pageshowhide
explore
- Reflection and Introspection17 questions
- Runtime Type Discovery5 questions
- Dynamic Invocation4 questions
- Accessibility Overrides4 questions
- Cost and Static Analysis4 questions
- Annotations and Decorators17 questions
- Declaring Metadata Types5 questions
- Retention and Visibility4 questions
- Metadata Consumers4 questions
- Configuration or Instruction4 questions
- Code Generation18 questions
- Build-Time Processors5 questions
- Schema-Driven Output5 questions
- Output Hygiene and Debugging4 questions
- Emit or Inspect4 questions
- Macros and Compile-Time Metaprogramming13 questions
- Tokens or Syntax Trees4 questions
- Hygiene and Capture4 questions
- Expansion and Staging5 questions
- Internal DSLs12 questions
- Fluent Call Chains4 questions
- Scoped Receivers4 questions
- When a Notation Pays4 questions
- Dynamic Proxies and Interception19 questions
- Synthesized Stand-Ins4 questions
- Compiled-Code Rewriting4 questions
- Interceptor Chains6 questions
- Blind Spots and Leaks5 questions
questions
96 · 6 sectionsWhat does a type's run-time self-description contain that lets a renderer build one column per field of an unknown object?
basics
~20 sA loaded type hands back a structured description of itself: its members with their names and declared types, modifier and visibility flags, its supertypes and the contracts it implements, and the type's own run-time identity.
A mapping layer reads each field by name at run time — what work does that add over a compiled field read?
basics
~20 sEach read first resolves a name to a member, then passes an access check, then moves the value through a generic representation and an indirect call. A compiled read did the resolving once, at build time, and does none of the rest.
A reflective write to a private field is refused before it runs — which boundary refused, and what must its owner grant?
basics
~20 sTwo layers guard a hidden member: the per-member access check, and, where a runtime groups types into packaged units, that unit's encapsulation boundary. A refusal of the suppression request itself points at the outer one, which only the unit's author or the deployer can open.
A container picks a constructor at run time from the argument values it holds — how does that selection differ from the compiler's?
basics
~20 sRun-time selection sees only the runtime classes of the actual values, never the declared types at a call site, so an absent value has no type at all, several constructors can match equally, and ambiguity surfaces at startup rather than in the build.
A walker lists a loaded type's members two ways and gets two different sets — which two axes separate the lists?
basics
~20 sTwo axes: where a member is declared (on this type or inherited from a supertype) and how visible it is (any visibility or public only). Common lookups bundle the two, so each call answers a fixed combination rather than letting you choose.
A marker attached to a class declaration changes nothing on its own — what must exist for it to have any effect?
basics
~20 sAttached metadata is inert: something has to read it. A consumer — a build-time processor, a startup scanner, or a check at the call site — must look for that marker and act on what it says, or nothing happens.
You declare a metadata type marking rate-limited operations. Why does its declaration name the kinds of declarations it may be attached to?
basics
~20 sA metadata type's declaration enumerates the declaration kinds it may be attached to - type, method, field, parameter - and attachments anywhere else are rejected. Target lists turn a misplaced marker into a build error instead of a silent no-op.
A marker on a payroll method quietly makes the call transactional and retried, so what does that cost the reader of the call site?
basics
~10 sAction-at-a-distance: the call site stops describing what the call does. Behaviour now depends on a marker plus machinery the reader cannot see, so local reasoning, debugging, review and onboarding all get more expensive.
A marker can be consumed by a build-time processor, a startup scanner, or a call-site check — when does each catch a mistake?
basics
~20 sA build-time processor catches a mistake while the build runs and can fail it. A startup scanner catches it when the process boots, after the artifact already shipped. A call-site check catches it only when that path actually executes.
Your published rate-limit metadata type gains a burst member. How do you keep other teams' existing attachments compiling?
basics
~20 sDeclare the new member with a default value. A member without a default must be supplied at every attachment site, so adding one breaks existing uses; a defaulted member is optional and older attachments keep their meaning.
Where should a hand-written addition to a generated telemetry type live, given the file is rewritten on every build?
basics
~20 sIn a hand-owned file the generator never writes, attached through an extension seam the generated type exposes: a type that extends it, a hook it calls, or a wrapper around it. Generated files are build output, not source.
Generated source is emitted into the hand-edited tree beside its inputs — what does that habit cost a team?
basics
~20 sMixing machine-written files into the hand-edited tree invites edits that a later re-derivation erases, and it feeds emitted code to style checks, coverage numbers and review. Emit into a separate output directory and mark every file derived.
How does a compiler-hosted processor tell the build which marked declarations it handles?
basics
~20 sA processor advertises up front the set of metadata markers it supports. Each round the build hands it only the declarations carrying those markers, and the processor then applies its own filter for declaration kind and shape before emitting anything.
Why is a processor's generated output fed through another processing round, and what makes the rounds stop?
basics
~20 sGenerated files are ordinary source: they can carry markers of their own, so the build re-runs processing over them. Each round's output becomes the next round's input, and the loop ends when a round emits no new file.
A command-line tool binds configuration onto objects by generated code or run-time inspection: which work does each leave for startup?
basics
~20 sGeneration decides the binding plan during the build, so startup only runs straight-line assignments. Run-time inspection computes the same plan inside the running process, so member discovery, key matching and conversion choice all land in the startup window.
When a macro body runs while the program is being compiled, which values can it splice into the code it emits?
basics
~20 sOnly values the expanding stage can render back as code: literals, names the emitted program can resolve, or fragments it built by quoting. A live compile-time object cannot be smuggled into run time; it has to be reconstructed.
Why can a shorthand that substitutes argument text before parsing compute the wrong value inside a larger expression?
basics
~20 sSubstitution happens before the grammar is applied, so the pasted characters are parsed together with whatever surrounds the call. The caller's operators can bind tighter than the body's, regrouping the expression into something the author never wrote.
A compile error points at a line nobody typed, inside a macro's output, so what makes that message actionable?
basics
~20 sSource positions carried on every emitted node, plus an expansion trace. Text derived from the caller's argument should point back at the call site, text the macro authored at the macro, and the trace should name the chain that produced it.
An expansion introduces a temporary binding into the caller's block - how can that silently change the caller's meaning?
basics
~20 sThe introduced binding shadows a caller name of the same spelling, so the caller's own references - including argument text spliced into the body - resolve to the expansion's temporary instead. This is accidental capture, and it compiles cleanly.
When a compile-time expansion substitutes one argument expression at two places in its body, what does the caller lose?
basics
~20 sSubstitution copies the argument's expression, not its value, so the expression is evaluated once per occurrence that control flow reaches. Side effects and cost repeat, and two evaluations can disagree. Bind it once into a temporary instead.
In a chained configuration API, what makes one call follow another, and what does the closing call add?
basics
~20 sEach configuring call returns a receiver carrying everything recorded so far, so the next call can be written straight onto it. The closing call is different: it validates the accumulated state and produces the finished result.
In a nested policy notation built from blocks, what does it mean that each block runs against an implicit receiver?
basics
~20 sEach block is a piece of code the enclosing call evaluates against an object it supplies, so unqualified calls inside the block configure that object. Nesting blocks builds a tree: an inner block targets the node its enclosing call just created.
When a host-language notation replaces a plain configuration API, what discoverability does a newcomer lose at the call site?
basics
~20 sA plain API advertises itself: its members, signatures and defaults are all reachable from the call site. A notation replaces that enumerable list with vocabulary a newcomer must be told about, and has to publish it some other way.
In a nested policy notation, why can a call inside an inner block silently configure the enclosing node instead?
basics
~20 sEnclosing receivers stay in scope when an inner block opens, so a name the inner node cannot answer is looked up outward and answered by the enclosing node. The call is legal, nothing is misspelled, and the wrong node is configured.
In a design review, which signals say a proposed domain notation will not repay its cost?
basics
~20 sThe notation does not repay when its authors are the people who wrote it, when its whole gain is punctuation an ordinary helper would remove, when its words are a one-for-one rename of existing members, or when it has begun growing its own control flow.
A caching wrapper speeds up calls from outside an object but never fires for a call the object makes on itself, why?
basics
~20 sInterception only runs where a call crosses from one object into another. A wrapper sees the calls made through the reference it was handed; a call the object makes on itself travels through its own self-reference and never reaches the wrapper.
Which calls does a stand-in that intercepts by overriding members silently miss, even when every caller goes through it?
basics
~20 sAnything that is not a dynamically dispatched call on the instance: members no subtype may replace, members resolved from the declared type or with no receiver at all, direct field reads, and work the target did while it was being constructed.
When a request passes through a chain of interceptors around one call, in what order do their after-the-call parts run?
basics
~20 sIn reverse of the order they were entered. Handlers nest rather than queue: each one calls inward and waits, so the handler entered last is the first to resume, and the outermost handler finishes last.
Which run-time stand-in works when the caller holds a concrete class rather than a declared contract, and what must that class allow?
basics
~20 sOnly a stand-in generated as a subtype of that class can be substituted there; in a nominally typed language a contract-only stand-in is not of that type. The subtype needs an extensible class, overridable members and construction that is safe to repeat.
A chat client's remote-service handle is synthesized at run time from a declared contract: what does each generated member body do?
basics
~20 sEvery generated member body does the same thing: it packages a description of the member that was called together with the argument list, hands both to one handler object, and returns whatever that handler returns.