A type declared output-only in its placeholder adds a member taking that placeholder as a parameter — what happens?
answer
- the promise covers every future caller
- checked where it is declared
- the checker walks every member signature
- the error lands on the member
- give the member its own placeholder
basics
~20 sThe declaration itself is rejected, not any call site. A type that claims one direction must keep every mention of the placeholder on that side, and the checker reads each member signature to enforce it before any caller exists.
solid answer
~40 sIt is a whole-type check performed where the type is declared. Once a placeholder claims the output-only direction, the checker walks every member signature and flags any mention of that placeholder in an incoming position — a method parameter, the incoming value of a writable property, a nested position that works out to an input. The error lands on the offending member, and no call site is involved, because the claim has to hold for every possible caller before there are any. The ways out are to drop the member, move it to a separate type, or give the member its own placeholder related to the type's by a limit, so that the type's placeholder no longer appears on the input side at all.
code
pseudocode · 9 linestype ReportView<T>: // T is declared output-only
function nextRow() returns T // accepted: T is a result
function addRow(value T) // rejected: T is a parameter here
// same intent, expressed without putting T on the input side
type ReportView<T>: // T is declared output-only
function nextRow() returns T
function copyInto<R>(sink LineSink<R>) // accepted: only R is a parameter type
where T is a kind of Rgo deeper
Know that the direction claim is enforced by the compiler rather than by convention, and that a member contradicting it stops the declaration from compiling.
Explain that the check runs over every member signature at the declaration, and name at least one way to keep the operation without putting the placeholder on the forbidden side.
When a colleague reports this error, tell them which of the three fixes suits the call sites rather than reflexively deleting the member or abandoning the direction claim.
Weigh whether a shared type keeps its direction claim at the cost of a more elaborate member signature, given that every downstream team's substitution freedom depends on it.
## A claim about every caller, checked once When a placeholder is declared output-only, the declaration is making a promise to everyone who will ever use the type: *values of this placeholder only leave; none ever enter*. That promise is what licenses callers to substitute one instantiation for another. A promise of that shape cannot be verified per call, because the set of calls is open — the type may be used by code that does not exist yet. So it is verified where it is made, against the only artefact that exists at that moment: the member signatures. That is why the answer to this question is *the declaration does not compile*, and why the interviewer is really asking whether you understand the scope of the guarantee rather than the wording of an error. ## What the checker walks 1. Every member of the type, in declaration order but independently of it. 2. Every occurrence of the declared placeholder inside each signature — parameter types, return types, property types, and the type arguments nested inside those. 3. For each occurrence, which side it lands on after nesting is taken into account: a plain parameter is an input, a return type is an output, and a placeholder nested inside a function-typed parameter can work out to either. 4. The result against the claim: an output-only placeholder may appear only where the reading says output, and an input-only placeholder only where it says input. An occurrence on the forbidden side is an error on **that member**, with the rest of the type unaffected. It is worth saying this precisely in an interview, because a vague "it won't compile" leaves open whether the speaker thinks the type, the member, or some call is at fault. ## The three ways out | fix | what it does | what it costs | |---|---|---| | drop the member | removes the only occurrence on the forbidden side | callers lose that operation entirely | | move it to a separate type | keeps the operation, on a surface with the opposite direction | callers must hold two references, or the concrete type | | give the member its own placeholder | the member's parameter is typed by a fresh placeholder, and the type's own placeholder appears only in the limit relating them | a more elaborate signature, and not every type system offers it | The third is the interesting one and the one that separates a middle answer from a shallow one. The type's placeholder is not banned from the member outright — it is banned from appearing *in an input position*. Restating the member so that what actually arrives is typed by a different placeholder, with the type's own placeholder mentioned only in the constraint that relates the two, satisfies the checker without losing the operation. Type systems differ in whether they provide this shape at all. ## The nested case that looks illegal and is not A member whose parameter is itself a function taking the placeholder is often flagged by a reader as a violation, and it is not. Going into a parameter position flips the direction, and going into the parameter of that parameter flips it back — so a member that hands rows to a supplied callback has the placeholder on the *output* side, which is exactly where an output-only claim permits it. The type is still the side producing rows; it has just chosen to push them instead of return them. The checker gets this right by construction because it reads positions recursively; human reviewers get it wrong by stopping at the first parameter list. ## What this buys, and what it does not The check is not bureaucracy. Without it, the direction claim would be an unchecked comment, and a caller could substitute instantiations on the strength of a promise no one verified — with the failure surfacing much later, inside the type, as a value of the wrong shape arriving where the code did not expect one. The checker converts a promise about every future caller into a finite inspection of the members that exist now. What it does not settle is where the direction is stated in the first place — once on the declaration, or separately at each place the type is used. That is a different subject, and a candidate who conflates the two usually ends up arguing about notation instead of about the guarantee. Here the point is narrower and firmer: whatever direction has been claimed, every member signature has to agree with it, and the compiler finds the one that does not.
- Why is the error reported on the declaration rather than at an unsafe call?Because the direction claim is a promise to all callers, including ones not yet written. A per-call check could only ever cover the calls that exist, so the guarantee is established once, against the member signatures, before any caller can rely on it.
- Does the check look only at a member's top-level parameters?No, it reads positions recursively through nested type arguments. A placeholder inside a function-typed parameter flips direction once more and can land back on the allowed side, so a member taking a callback that receives the placeholder is legal on an output-only type.
- Does the same check apply to an input-only claim?Yes, mirrored. An input-only placeholder may appear only where the reading works out to an input, so a member returning it is rejected on exactly the same grounds — a returning member would hand values out, which is the direction that claim rules out.
saying these in an interview costs you the question
- Expects the error at the call site that passes a value
- Thinks the check inspects only return types
- Believes reordering the members can satisfy the checker
- Assumes any offending member can be kept by casting the parameter
- Treats a callback parameter as automatically forbidden on an output-only type