skip to content

In TypeScript, given `declare function pair<K, V>(k: K, v: V): [K, V]`, can you call `pair<string>('a', 1)` to pin K and let V be inferred? What is the rule for how many type arguments a call may supply?

level: middleimportance: should knowfreq 48%

answer

  1. angle-bracket lists are positional
  2. no placeholder for a skipped entry
  3. all required ones, or none
  4. explicit arguments switch inference off
  5. currying buys a second list

basics

~20 s

No. TypeScript has no partial type-argument inference: a call supplies every type parameter that has no default, or none at all. Omitted trailing parameters take their declared defaults rather than being inferred from the arguments.

solid answer

~50 s

You cannot. `pair<string>("a", 1)` is an arity error — the compiler expects two type arguments and got one. The rule is that a type-argument list must be either empty, in which case everything is inferred, or long enough to cover every parameter without a default; only trailing *defaulted* parameters may be left off, and there is no placeholder to skip an earlier one. Crucially, a default does not rescue you here either: writing any explicit type arguments turns inference off for the whole call, so an omitted defaulted parameter takes its default instead of being inferred. When you genuinely need one parameter pinned and one inferred, the usual moves are to curry — a generic function that returns a generic function, so each list is supplied separately — or to reshape the API so the parameter you wanted to pin is inferable from an argument.

code

typescript · 14 lines
typescript
declare function pair<K, V>(k: K, v: V): [K, V];

pair("a", 1);                 // ok - both inferred: [string, number]
pair<string, number>("a", 1); // ok - full list supplied
// pair<string>("a", 1);      // error: Expected 2 type arguments, but got 1.

// Currying gives one explicit list and one inferred list.
function pinKey<K>() {
  return function <V>(k: K, v: V): [K, V] {
    return [k, v];
  };
}

const p = pinKey<string>()("a", 1); // [string, number]

go deeper

for a junior

Recall that a call either supplies all the type arguments or none, and that mixing the two produces an arity error rather than a partially inferred call.

for a middle

Explain the precise rule — every parameter without a default, or nothing — and demonstrate that writing any explicit argument disables inference so defaulted parameters fall back rather than being inferred.

for a senior

Show that signature shape determines usability: put frequently pinned parameters first, default the rest, and reach for currying or an inferable argument when a caller genuinely needs one pinned and one inferred. Be able to diagnose the confusing assignability error a half-supplied list produces.

for a principal

Own this as an API constraint rather than a coding trick: how many type parameters a public generic exposes, and in what order, decides whether callers can express what they need without contortions. Argue for fewer parameters driven by inference over long lists that force callers to spell everything out.

## The rule A call site may supply type arguments explicitly, but the list is not a place to fill in some entries and leave the rest to the checker. The list must contain at least as many entries as there are type parameters **without** a default, and at most as many as there are parameters in total. For `pair<K, V>` neither parameter has a default, so the only legal lists are zero entries (infer everything) or two entries (specify everything). `pair<string>("a", 1)` fails with an arity error saying two type arguments were expected but one was given. This is often summarised as *all or nothing*, which is close enough for interviews as long as you can state the precise version: **all the required ones, or none**, and there is no way to skip an earlier entry to reach a later one — TypeScript has no `_` placeholder for a type argument, and no syntax for partial type-argument inference. ## Why defaults do not rescue you The obvious idea is to give `V` a default so the shorter list becomes legal. It does become legal, but it stops doing what you wanted: ```ts declare function pair<K, V = boolean>(k: K, v: V): [K, V]; pair("a", 1); // no explicit arguments -> both inferred -> [string, number] pair<string>("a", 1); // legal arity, but V = boolean, so 1 is not assignable ``` The moment you write any explicit type argument, inference is switched off for the entire call. The compiler takes your list and fills the remaining trailing entries from their declared defaults; it does not go back and infer them from the arguments. So the defaulted parameter becomes a fixed type, and an argument that does not match it is an error — usually a very confusing one, because the argument looks obviously fine. ## Why the language works this way Type-argument lists are positional, exactly like value-argument lists, so there is nowhere to put "skip this one". Supporting partial lists would mean inventing a placeholder and then deciding how inference interacts with the entries you did supply — which candidates are still collected, whether a supplied entry constrains an inferred one, and what happens with overloads. It has been a long-standing request rather than a feature; the practical consequence for you is that the *shape* of a generic signature decides what a caller can and cannot pin. ## Workarounds that actually work **Curry the generics.** A generic function returning a generic function gives you two separate type-argument lists, so the first can be explicit while the second is still inferred: ```ts function pinKey<K>() { return function <V>(k: K, v: V): [K, V] { return [k, v]; }; } const p = pinKey<string>()("a", 1); // [string, number] ``` The cost is an extra call and a slightly odd-looking API, which is why this shows up mostly in library code. **Make the parameter inferable.** Often the reason you wanted to pin `K` is that nothing in the arguments determines it. If you can pass a value whose type carries that information — an options object, a schema, a sentinel argument — inference does the work and no explicit list is needed at all. **Order the parameters so the pinnable ones come first.** Since only trailing defaulted parameters can be omitted, a signature where the frequently-specified parameter is first and the rarely-specified ones are last and defaulted is far more usable than the reverse. **Annotate the result instead.** Sometimes the goal is not really to pin a parameter but to fix the result type; a contextual type on the receiving variable or an explicit return annotation can achieve that without touching the type-argument list. ## The same rule applies to type references This is not a call-site quirk. A generic *type* reference obeys the same arity rule: `Map<string>` is an error because `Map<K, V>` declares two parameters and neither has a default, while `Envelope<User>` is fine when the second parameter of `Envelope` is defaulted. Nothing infers type arguments in a type position, so there the list is genuinely all-or-required. ## What to say in an interview State the rule, give the arity error, and then show that you know the defaults trap: adding a default changes what is *legal* to write, not what is *inferred*, and a half-supplied list silently stops inferring the rest.

  • If the trailing type parameter has a default and I supply only the first type argument, what type does the second parameter end up with?
    Its default — not an inferred type. Explicit type arguments switch inference off for the whole call, and the compiler fills the missing trailing entries from their declared defaults. With `pair<K, V = boolean>`, the call `pair<string>("a", 1)` fixes V as boolean and then rejects the numeric argument, which is a confusing error unless you know this rule.
  • Does the same arity rule apply to generic type references, not just calls?
    Yes, and more strictly, because nothing is ever inferred in a type position. A reference must supply every parameter that lacks a default: `Map<string>` is an error since both of Map's parameters are required, while a type whose trailing parameter is defaulted accepts either the short or the long form.
  • How does currying let you pin one type parameter and infer another?
    Each generic function has its own type-argument list, so a generic function that returns a generic function gives you two. You supply the outer list explicitly and let the inner one be inferred from the real arguments. It costs an extra call and a less obvious signature, which is why it stays mostly in library code.

A type-argument list behaves like a positional argument list: you can drop trailing entries that have defaults, but there is no way to skip a middle one and fill in the rest.

saying these in an interview costs you the question

  • Says the unsupplied parameter is inferred as usual
  • Thinks omitted type arguments become any or unknown
  • Believes there is a placeholder syntax for skipping a type argument
  • Adds a default expecting it to enable partial inference
  • Claims the rule differs between calls and type references

context