You pass a generic component `function List<T>(props: ListProps<T>): string` to a helper declared as `function wrap<P>(component: (props: P) => string): (props: P) => string`. The wrapped result no longer infers the item type at each usage. Which TypeScript rule causes that, and what is the standard fix?
answer
- polymorphism survives only in a matching slot
- the target signature has no room for the parameter
- something quietly picks a value for T
- unknown is what it picks
- the fix is one unchecked line
basics
~20 sThe helper's parameter is an ordinary, non-generic function type, so passing a generic function instantiates its type parameter once — usually to unknown — and the result is a single concrete component. The standard fix is to assert the wrapped value back to the original signature.
solid answer
~50 sTypeScript preserves a generic signature only when the target position is itself generic. `wrap` declares its own parameter `P` and expects a plain `(props: P) => string`, so passing `List` makes the checker choose one instantiation of `T` to satisfy that shape; with nothing to infer from, `T` collapses to `unknown` (or its constraint) and `P` becomes `ListProps<unknown>`. The returned component is therefore monomorphic and every usage shares one element type. The idiom is to assert the result back: `const WrappedList = wrap(List) as typeof List;`. That is exactly the trick applied to `React.memo`, whose declaration has this same non-generic props shape. The assertion is unchecked, so keep it on one line next to the wrap call, and remember it also discards any extra members the wrapper's return type carried. The alternative is to do the wrapping inside a generic function, so the parameter is still in scope.
code
typescript · 17 linesinterface ListProps<T> {
items: readonly T[];
renderItem: (item: T) => string;
}
declare function List<T>(props: ListProps<T>): string;
declare function wrap<P>(component: (props: P) => string): (props: P) => string;
// Collapsed: T was instantiated to unknown at the wrap call
const WrongList = wrap(List);
// Restored: the assertion re-states the original generic signature
const WrappedList = wrap(List) as typeof List;
// T is inferred as string again at this usage
const html = WrappedList({ items: ["a", "b"], renderItem: (s) => s.toUpperCase() });
console.log(html, WrongList);go deeper
Recognise the symptom: after a component is passed through a wrapper helper, its item type stops being inferred and errors start mentioning unknown at the usage sites.
Explain that a generic function passed into a non-generic function-typed parameter is instantiated once, name unknown as the fallback, and show the assertion that restores the original signature.
Say plainly what the assertion costs — it is unchecked and discards anything the wrapper's return type added — and compare it against wrapping inside a generic factory, which is sound but sacrifices referential identity.
Set the policy: where unchecked assertions are tolerated in shared component code, how they are reviewed and commented, and whether the library exposes pre-wrapped components at all rather than asking every consumer to reapply the trick.
## Generic in, concrete out A generic function's type is a signature with unfilled slots: `<T>(props: ListProps<T>) => string`. A parameter typed `(props: P) => string` — where `P` is the *helper's* own parameter, fixed at that call — is a signature with no slots. When you pass the first where the second is expected, TypeScript does not carry the polymorphism through; it **instantiates** the argument's type parameters so that the result matches the target. That is the whole rule, and it explains the symptom: after `wrap(List)`, there is no `T` left to infer, so every usage of the wrapped component shares one element type. What does `T` become? Inference for it runs against the target signature, which mentions `T` nowhere useful, so it falls back — to the constraint if there is one, otherwise `unknown`. `P` then infers as `ListProps<unknown>`, and callers see errors that look nothing like the real cause: an object of `User` values not assignable to `readonly unknown[]`'s neighbours, a render callback whose parameter is `unknown`. TypeScript can preserve genericity, but only positionally: assigning `<T>(x: T) => T` to a variable declared `<U>(x: U) => U` keeps the signature generic, because the target has a slot to keep it in. Inferring *into* a type parameter, as `wrap` does, is the case that collapses. The general feature that would make `wrap` work — a parameter that ranges over generic signatures — is higher-rank polymorphism, and TypeScript's inference does not provide it. ## The fix everyone uses ```typescript interface ListProps<T> { items: readonly T[]; renderItem: (item: T) => string; } declare function List<T>(props: ListProps<T>): string; declare function wrap<P>(component: (props: P) => string): (props: P) => string; // collapsed: WrongList is (props: ListProps<unknown>) => string const WrongList = wrap(List); // restored: the assertion re-states the generic signature const WrappedList = wrap(List) as typeof List; ``` `typeof List` is the generic signature verbatim, so the asserted constant behaves at usage sites exactly like the original: `T` is inferred per call again. This is the identical idiom applied to React's `memo`, which is declared with its own props type parameter and therefore hits precisely this rule — `const MemoList = memo(List) as typeof List;` is the line you will find in real codebases and the one an interviewer is fishing for. Be honest about what the assertion costs. It is unchecked: the compiler now believes the wrapped value has `List`'s type on your say-so, and if the helper's real return type differs in any way that matters — extra members you intended to use, a different call shape — you have hidden that. It also throws away anything the wrapper's return type added beyond the call signature. Keep the assertion adjacent to the wrap call so a reader sees both, and never widen it into a reusable helper that asserts arbitrary wrappers. ## The alternative: keep the parameter in scope If you control the call site, you can avoid the assertion by wrapping *inside* a generic function, where `T` is still bound: ```typescript function makeWrappedList<T>(): (props: ListProps<T>) => string { return wrap<ListProps<T>>(List); } ``` This is honest — no assertion — but it moves the instantiation to whoever calls the factory, which for a memoised component defeats the purpose, since each call produces a fresh wrapped value. That tradeoff is the substance of a senior answer: the assertion buys per-usage inference at the cost of one unchecked line, and the factory buys soundness at the cost of identity. ## Where else the collapse shows up The same rule fires whenever a generic function meets a non-generic function-typed slot: storing a generic function in a registry typed `Record<string, (x: SomeProps) => string>`, passing one as a callback whose parameter type is already fixed, or spreading it through a decorator-shaped helper. The tell is always identical — the type parameter silently becomes `unknown` and errors surface at the *callers* rather than at the assignment. When you see `unknown` where you expected an inferred type, look for the last place the value passed through a non-generic signature. ## Answering it well Name the rule (a generic signature is instantiated when the target is not generic), name the resulting instantiation (`unknown` or the constraint), give the assertion idiom, and state its risk. Candidates who only recite `as typeof List` without the rule usually cannot explain why it works, and the follow-up about what `T` became exposes that immediately.
- What does the type parameter actually become when the collapse happens?Its constraint if it has one, otherwise `unknown`. Inference runs against the target signature, which offers no candidate for it, so the checker falls back. That is why the downstream errors mention `unknown` — an item list stops being assignable and the render callback's parameter becomes unusable — even though the assignment itself compiled without complaint.
- What are the risks of the `as typeof List` fix?It is an unchecked assertion: the compiler stops verifying that the wrapped value really has that type, so a mismatch between the helper's actual return type and the original signature is silently accepted. It also discards any members the wrapper added beyond the call signature. Keep it on the same line as the wrap call and never generalise it into a reusable any-wrapper helper.
- Is there a way to keep genericity without an assertion?Yes, by keeping the parameter in scope: wrap inside a generic function, `function make<T>() { return wrap<ListProps<T>>(List); }`. No assertion is needed because `T` is still bound. The cost is that every call produces a fresh wrapped value, which destroys referential identity — usually unacceptable for a memoising wrapper, which is exactly why the assertion is the common answer.
A generic function is a blank form; a non-generic parameter slot is a filing cabinet that only accepts filled-in forms. Handing one over means someone fills in the blank on the spot — and the copy in the cabinet can never be filled in differently again.
saying these in an interview costs you the question
- Thinks the helper stays generic because its argument was
- Says the type parameter becomes any rather than unknown
- Treats the assertion as a checked conversion
- Blames the component's props type instead of the wrapper's signature
- Claims TypeScript supports inferring a generic signature into a parameter