skip to content

A generic filter helper is called with an empty basket that names no element type, so how is the placeholder resolved?

level: middleimportance: must knowfreq 62%

answer

  1. no element, no evidence
  2. an empty expression is itself generic
  3. empty at run time is not the problem
  4. look for a target type instead
  5. the bound is the silent fallback

basics

~20 s

An empty input contributes no evidence, so the placeholder has to come from elsewhere: the expected type at the call's position, a declared bound or default, or an explicit type argument. Otherwise the call widens uselessly or is rejected.

solid answer

~50 s

An empty container expression carries no element whose type could constrain the placeholder, and the expression is usually generic itself, so it forwards the question rather than answering it. The solver then needs evidence from another direction: the type the result is expected to have where the call sits, a bound declared on the placeholder, a language-supplied default, or the type argument written by hand. With none of those, the outcome depends on the language's rules - the placeholder may default to its bound, which for an unbounded placeholder is the universal supertype, or the call may simply be rejected until you name the type. Keep one distinction clear: a container that is merely *empty at run time* but declared as a basket of fruit constrains the placeholder perfectly well. What breaks the solve is being empty of **evidence**, not empty of elements.

code

pseudocode · 15 lines
pseudocode
function newEmptyBasket<T>() -> Basket of T
function filterBasket<T>(basket: Basket of T, keep: Test of T) -> Basket of T

# 1. nothing constrains the placeholder from either direction
display(filterBasket(newEmptyBasket(), acceptAnything()))

# 2. the declared target supplies it from above
fruit: Basket of Fruit = filterBasket(newEmptyBasket(), acceptAnything())

# 3. written by hand, so no evidence is needed
display(filterBasket<Fruit>(newEmptyBasket(), acceptAnything()))

# 4. empty at run time, but the declaration is still evidence
stock: Basket of Fruit = newEmptyBasket<Fruit>()
left = filterBasket(stock, acceptAnything())

go deeper

for a junior

Remember that an empty input gives the compiler nothing to work from, and that declaring the variable the result goes into is usually enough to make the same call compile.

for a middle

Explain which evidence is missing and which three sources remain, and separate an expression with no declared element type from a container that is merely empty when the program runs.

for a senior

Describe the silent failure: a placeholder defaulted to its bound compiles now and produces narrowing and mismatch errors far from the call, so treat a widened inference as a defect rather than a convenience.

for a principal

Set the expectation for signatures your teams publish: a placeholder that appears only in the result position taxes every caller with an annotation, and that is a design decision, not a call-site accident.

## Why an empty input is a different kind of argument Most arguments answer the solver's question by accident. Pass a basket declared as a basket of fruit into a position declared as a basket of `T`, and the placeholder is pinned to fruit without anyone thinking about it. An empty container expression - a freshly built container with nothing in it, or a factory call that produces one - breaks that habit in a specific way: the expression itself has an open element type. It is not that the solver looks inside and finds nothing; it is that the expression **is generic too**, so passing it adds an unknown to the system instead of a constraint. The practical shape is a call where every piece is a placeholder waiting on some other piece. That is the situation interviewers are probing when they ask about inference limits, because it is the first place a working engineer meets an error message about a type argument that "cannot be determined". ## Empty of evidence is not the same as empty at run time This distinction separates a good answer from a vague one: - A variable **declared** as a basket of fruit that holds nothing at the moment still contributes *fruit*. Its declaration is the evidence, and run-time emptiness is invisible to a compile-time solve. - An **expression** with no declared element type - a new empty container, an empty-container factory call - contributes nothing, whether or not anything would ever be put in it. - A container declared at the wide end, such as a basket of arbitrary goods, contributes that wide type. It does not fail; it succeeds at a type you probably did not want, which is a different and sneakier problem. ## What the solver has left With no evidence from the arguments, three sources remain, and a good answer names them in order of how often they apply: 1. **The expected type where the call sits.** Assigning the result to a variable declared as a basket of fruit, or returning it from a routine with that declared result type, gives the solve its answer from above. This is why the identical call compiles in one position and fails in another. 2. **A declared bound or default.** A bound caps the candidates and, in languages that default an unsolved placeholder, becomes the answer. A default type argument named on the declaration does the same job deliberately. 3. **The explicit type argument.** Naming the type once at the call site ends the discussion, and is the fix the error message is usually asking for. ## Two ways compilers land Languages genuinely differ here, and saying so is part of a complete answer. | Behaviour | What the caller sees | What it costs | |---|---|---| | Refuse the call | A compile error naming the placeholder that could not be solved | Nothing but a one-line fix, and the error points at the missing evidence | | Default to the bound | The call compiles; the result is a container at the bound, the universal supertype when nothing was declared | Reads come back at the wide type and need narrowing, and the value will not fit where a specific-element container is expected | The second row is the one that hurts. A silently widened result is not a compile error moved somewhere else; it is a compile error turned into a later, less obvious one. The value is perfectly legal and perfectly useless: it can still be passed around, and every attempt to use it as a basket of a particular element type is rejected far from the call that caused it. ## Fixing it at the call site Four moves, roughly in order of preference: - **Declare the receiving variable.** The cheapest fix when the result is going somewhere anyway: the declaration supplies the ceiling and the solve completes. - **Write the type argument on the call.** Best when the result is not assigned - when it is passed straight on, or discarded - because there is no declaration to hang the evidence on. - **Pass an already-typed empty container.** If the same empty value is used at several call sites, giving it a declared type once removes the problem from all of them. - **Change the signature.** If a placeholder appears only in the result position, no argument can ever constrain it and every call depends on having a usable expected type. Mentioning the placeholder in a parameter gives the solver evidence at every call site. ## Why interviewers like this case It separates the candidate who has memorised "the compiler figures it out" from the one who can say *what it figures it out from*. The empty container is the smallest example where the machinery becomes visible, and the follow-up - why does the same call compile after you declare the variable it lands in? - is the natural test of whether the model is real.

  • Does a container that happens to be empty at run time cause the same inference failure?
    No. Its declaration already names the element type, and that is what the solve reads; emptiness at run time is invisible to a compile-time solver. The failure belongs to an expression with no declared element type, such as a freshly built empty container or an empty-container factory call whose own placeholder is still open.
  • When the placeholder defaults to its bound instead of failing, why is that often worse than an error?
    Because the call compiles and the damage surfaces later. You hold a container typed at the bound - the universal supertype when none was declared - so reads come back at that wide type and need narrowing, and the value will not fit where a container of a specific element type is expected. An error at the call site would have pointed straight at the missing evidence.
  • Is the same problem possible when the empty container is the result rather than the argument?
    Yes, and it is the harder version. A helper that only returns an empty container mentions its placeholder nowhere in the parameter list, so no argument can constrain it; every call depends entirely on the expected type. Used as an argument to something else, or discarded, such a call needs an explicit type argument every time.

saying these in an interview costs you the question

  • Thinks an empty container still carries its element type at run time
  • Says the compiler will take the type of the first element
  • Believes a container that is empty at run time also breaks inference
  • Assumes defaulting to the universal supertype is a usable result
  • Claims the only fix is to make the helper non-generic
  • Reaches for a cast on the result instead of supplying the missing type