When a basket helper's inferred type argument is ambiguous or wider than intended, what decides between naming it explicitly and restructuring the call?
answer
- two failures, one fix each
- ambiguity stops the build, widening does not
- count the call sites
- a repeated annotation accuses the signature
- placeholder only in the result position
basics
~20 sCount the call sites. One call that genuinely knows a type the solver cannot see deserves an explicit type argument; the same annotation appearing at many call sites accuses the signature, which is handing the solver too little evidence.
solid answer
~50 sTwo failures look alike at the call site and want different answers. **Ambiguity** means several types satisfy every constraint and none is more specific - typically because arguments of unrelated types were passed into positions the signature ties to one placeholder, and their common supertypes are incomparable. **Widening** means the solver did find an answer, just a wider one than you meant, because it climbed to a common supertype of mixed arguments. In both cases the explicit type argument works, and it is the right fix when the call is a one-off and the intended type genuinely lives only there: it is local, it costs one place, and it documents the intent. Restructure instead when the annotation keeps reappearing, when the arguments were never meant to share a placeholder, or when the inferred result is wide enough that later code will have to narrow it.
code
pseudocode · 12 linesfunction firstAvailable<T>(a: T, b: T) -> T
# both arguments are tied to one placeholder, and their common
# supertypes are incomparable: no single most specific solution
pick = firstAvailable(applePack, discountVoucher)
# the escape: state the intended type once, at the call
pick = firstAvailable<Sellable>(applePack, discountVoucher)
# restructure instead: the values were being carried too wide
item: Sellable = applePack
pick = firstAvailable(item, discountVoucher)go deeper
Know that you are allowed to write the type argument yourself when a call does not infer what you meant, and that doing so is normal rather than a sign something is broken.
Separate the two conditions: ambiguity means no most specific solution exists, while widening means one was found at a type more general than intended.
Show the operating judgement - fix the call that genuinely knows the type, and treat the same annotation repeated across many call sites as evidence about the declaration rather than about its callers.
Own the standard: decide when a published helper may require callers to name a type argument, since that cost is paid by every consuming team and is invisible in the declaration.
## Two failures that look alike At the call site both show up as "the type is not what I wanted", but they are different conditions and it is worth naming which one you have. - **Ambiguity.** Several candidate types satisfy every constraint and none is more specific than the others. The usual cause: two arguments of unrelated types land in positions the signature ties to the *same* placeholder, and the types they share in common are incomparable to each other. There is no least solution to pick. - **Widening.** The solver succeeded, but climbed to a common supertype to do it. The call compiles and the result carries a type wide enough that every later use needs narrowing, or fits no position that expects the specific element type. Ambiguity is loud and stops the build. Widening is quiet, and is the one that reaches production. ## Why a solver goes wide When two arguments of different types constrain one placeholder, the solve has to find a type that accepts both. That means climbing until the two lines meet. Sometimes they meet somewhere sensible, sometimes at a type so general it carries no useful operations, and sometimes at several points that cannot be ordered against each other - which is exactly when widening turns into ambiguity. What the solver will not do is invent a second placeholder: the signature is fixed, and if it says two positions share one type, the solve honours that even when the call clearly did not mean it. ## The explicit type argument Naming the type argument at the call is a first-class part of the language, not a defeat. - It is **local**: it changes one call and nothing else, so it is safe to apply while you think about the better fix. - It is **precise**: it states exactly the type you meant, rather than nudging the solve with a cast on the result, which changes nothing about what the call returns. - It is **readable at the point of confusion**: a reader who wondered what the call produces stops wondering. - It is **cheap to remove** later, once the signature or the call is reshaped. The one thing it does not do is scale. Every call site that needs it pays the same cost, and nothing about the declaration records why. ## Deciding: read the count, not the instance | Symptom | At one call site | Across many call sites | |---|---|---| | Nothing constrains the placeholder | Write the type argument, or declare the receiving variable | The placeholder appears only in the result position - change the signature so a parameter mentions it | | Two arguments give incomparable solutions | Write the type argument to state the intent | The signature ties positions callers never meant to tie; the shape is wrong for how it is used | | Inferred type is wider than intended | Write the type argument, or narrow the declared type of an argument | Callers routinely pass mixed types - decide deliberately what the shared type is and publish it | The heuristic is worth stating plainly in a code review: **one annotation is information, a dozen identical annotations are a bug report about the declaration.** ## What restructuring actually means 1. **Declare the receiving variable** so the expected type does the work, and the reader gets a named intermediate for free. 2. **Split a nested call** so each part has a concrete type to be solved from. 3. **Narrow an argument's declared type** where a value was being carried around wider than it needs to be - often the real defect, with the inference complaint only a symptom. 4. **Change the signature** so the placeholder is reachable from a parameter rather than only from the result. ## The judgement an interviewer is listening for A weak answer treats every explicit type argument as a smell to be refactored away, and produces contortions to avoid one. An equally weak answer annotates everything and never asks why. The judgement being tested is the middle one: an explicit type argument is a cheap, honest statement of intent at a call that genuinely knows something the solver cannot see, and a *pattern* of them is evidence about the declaration rather than about any of the callers. The second half of the answer - noticing that a widened inference compiles and fails later - is what separates someone who has debugged this from someone who has only read about it.
- What in a signature makes call sites need explicit type arguments most often?A placeholder that appears only in the result position. Nothing in the parameter list can constrain it, so every call depends on having a usable expected type, and any call passed straight on as an argument or discarded needs the annotation. Mentioning the placeholder in a parameter gives the solver evidence at every call site instead.
- Two candidate solutions fit a call equally well - is that a defect in the caller or in the signature?Usually the caller, when it has passed unrelated types into positions the signature ties to one placeholder; naming the intended type argument resolves it. It becomes a signature defect when callers routinely do that, because the declaration is asserting an agreement between positions that real usage does not have.
- Why is a cast on the result a poor substitute for an explicit type argument?The cast happens after the solve, so the call still returns the wide or wrong type and the cast only asserts something about the value afterwards - checked at run time where the language checks casts at all. The type argument changes what is actually solved and checked, which is what you wanted.
saying these in an interview costs you the question
- Treats every explicit type argument as a smell to be refactored away
- Says the solver picks the narrowest type when two fit equally well
- Believes a wider inferred type is harmless because the call still compiles
- Casts the result instead of naming the type the call should solve for
- Assumes repeated annotations mean inference is unreliable rather than under-fed
- Thinks the solver will add a placeholder when two arguments disagree