Your platform team ships expansions that land inside other teams' blocks on a facility with no automatic renaming - what do you standardise?
answer
- the consumer cannot see the cause
- the author cannot enumerate consumer names
- reserve a name space, bind once
- force the collision in a test
- a plain function gives this free
basics
~20 sStandardise what the facility cannot guarantee: a reserved name space for every introduced binding, a bind-once rule for any argument used more than once, no dependence on caller-scope names, and tests that force the collisions deliberately.
solid answer
~50 sWithout automatic renaming, capture and repeated evaluation are not the author's private bugs - they are defects that appear in consumers' code and are diagnosed by people who cannot see the body. So the standard has to convert both into things a review and a test can check. Reserve a prefix or name space for every binding an expansion introduces, and forbid consuming code from using it; require any parameter used at more than one place to be bound once before use; require that the body depend on nothing it neither introduces nor receives as an argument, unless that dependence is explicit and part of the published contract. Then require each shipped expansion to carry a test that calls it from a block deliberately declaring the reserved names and passing an argument with a counted observable effect. Keep the surface small: the fewer expansions you ship, the less of this there is to police.
go deeper
Recall that an expansion published to other teams puts its bindings inside their code, which is why teams put rules around who may ship one.
Explain the two concrete hazards being policed - an introduced binding shadowing a consumer's name, and an argument evaluated once per path reached - and what a convention can and cannot guarantee about each.
Describe the test that forces the collision and counts the effects, and argue why a body must not depend on anything in the consumer's scope that is not in the published contract.
Own the surface decision: which cases truly need code rewritten before compilation, what you publish as contract, and when the right answer is an ordinary function instead.
## What changes when the caller is another team A capture bug inside one team's code is annoying. The same bug in an expansion published to other teams is different in kind, for three reasons: - **The person who hits it cannot see the cause.** They see one line naming a construct and a wrong value; the introduced binding lives in text they never read. - **The author cannot avoid it by choosing names.** Collisions depend on consumers' variable names, which the author does not know and cannot enumerate. - **It appears only for some consumers.** The expansion is correct in its own tests and in most callers, so the defect looks like the consumer's fault. When the facility renames introduced bindings automatically, most of this disappears. When it does not, the guarantee has to be manufactured out of convention, review and tests - and that is a standard someone must own. ## The four rules worth writing down 1. **A reserved name space for introduced bindings.** Every binding an expansion introduces carries an agreed prefix, and consuming code is forbidden from declaring names in that space. This does not make collision impossible; it makes it a rule violation on a known side, which is the most a convention can buy. 2. **Bind once.** Any parameter used at more than one place in a body is evaluated once into an introduced binding before the body uses it. This makes evaluation count independent of which path runs and of what expression the consumer passed. 3. **No silent dependence on the consumer's scope.** A body may use what it introduces and what it receives as arguments. Anything else - a helper, a constant - is either passed in or explicitly declared part of the contract, so no consumer's unrelated declaration can change the construct's behaviour. 4. **Small published surface.** Every expansion is a permanent obligation under rules 1 to 3. Publishing four is a policy you can hold; publishing forty is a policy you will discover you have not held. ## Making it checkable rather than aspirational A convention nobody can test decays. Two mechanisms make these rules real: - **The adversarial call test.** Each shipped expansion ships with a test that calls it from a block which deliberately declares variables spelled exactly like the bindings the body introduces, and passes an argument whose observable effect is counted. The test asserts that the consumer's variables are unchanged, that the arguments observed the consumer's data, and that the effect count matches the count the contract promises for each path. Capture and repeated evaluation are both invisible unless the collision is forced, so the test must force it. - **A mechanical scan of the body.** Names the body declares must match the reserved space; parameters occurring more than once must be bound first. Both are structural properties of the body, so a check can decide them without judgment, and a check that runs beats a rule that is remembered. ## What the contract must say | Published for each expansion | Why the consumer needs it | |---|---| | the reserved names it introduces | so a collision is attributable rather than mysterious | | how many times each argument is evaluated, per path | so passing an expression with an effect is an informed choice | | anything it expects the caller to have in scope | so an intentional crossing is never a surprise | | whether it is safe to nest inside itself | so a consumer knows the composition boundary | ## The trade-off you actually own The honest alternative is not a better convention - it is **not expanding at all**. An ordinary function evaluates its arguments exactly once, binds its temporaries in its own scope, and resolves its helpers where it was written. Every one of the four rules above is reconstructing, by hand and by review, a guarantee that a call gives away free. So the decision a lead owns is which handful of cases genuinely need code rewritten before compilation, and whether the cost of policing those is smaller than the cost of what they buy. Where that judgment lands depends on the facility. With automatic renaming, the standard shrinks to bind-once and the published contract, and expansion is a reasonable tool. Without it, every published expansion is an ongoing liability carried by people who will not be in the room when it misfires - which is a good reason to hold the surface to the few cases that cannot be a function. ## What an interviewer is listening for That you treat this as an ownership problem, not a cleverness problem: name the guarantee the facility does not provide, replace it with something checkable rather than something remembered, publish the evaluation count and reserved names as contract, and be willing to say that the real answer for most candidate cases is an ordinary function.
- A consuming team reports a wrong value they cannot explain. What do you ask for first?The expanded text for that call site, and the names declared in the enclosing block. Capture shows up as a spelling shared between their block and the reserved space, and repeated evaluation shows up as an argument with an observable effect. Both are visible in one look once the expansion is on the page.
- Is a reserved prefix a guarantee?No. It is a rule on a side you can police, not a property the toolchain enforces, so it fails silently the day a consumer legitimately uses the prefix. It is the best available substitute when there is no automatic renaming, which is why the adversarial test exists alongside it.
- When would you reject a proposal to publish a new expansion outright?When an ordinary function would do the same work. Expansion is worth its ongoing cost only when the construct must act before compilation - rewriting or inspecting the code it is given. Convenience, brevity or avoiding a little repetition do not clear that bar for a surface other teams consume.
saying these in an interview costs you the question
- Relies on unusual temporary names and calls the problem solved
- Pushes the rule onto consumers: do not name your variables that
- Tests the expansion only from a block with no other declarations
- Never publishes how often an argument is evaluated
- Treats an expansion as interchangeable with a function for any use
- Adds expansions freely because each one individually looks harmless