In TypeScript, `function make<T extends { name: string }>(): T { return { name: "anon" }; }` fails with "'{ name: string; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ name: string; }'". What is the compiler protecting against, and how do you fix it?
answer
- who picks T — not the function
- constraint is a lower bound
- consuming a T is fine, producing one is not
- type parameter only in the return position
- as T compiles and proves nothing
basics
~20 sThe caller chooses T, not the function, and may choose a subtype requiring more than name. A value that merely satisfies the constraint is therefore not a valid T. Fix the signature — return the concrete type or derive the value from a T the caller passed in.
solid answer
~50 sSatisfying the constraint is not the same as *being* `T`. The constraint is a lower bound the caller may go below: someone can call `make<{ name: string; id: number }>()`, and the compiler must guarantee they receive an object with an `id`. Your body only knows the constraint, so it cannot manufacture such a value — hence the error, which shows up identically when you assign a constraint-shaped literal to a variable or parameter of type `T`. The real fix is almost always that the signature is dishonest: if the function builds a fixed shape, return that concrete type and drop the type parameter from the return position; if it should preserve the caller's type, take a `T` as an argument and derive the result from it, for example returning `T & { createdAt: number }` from a spread. `as T` compiles but is an unchecked assertion that hands the caller a value missing the members you promised — a runtime `undefined` waiting to happen.
code
typescript · 25 lines// Rejected: the body cannot manufacture a value of the caller's chosen T.
// function make<T extends { name: string }>(): T {
// return { name: "anon" };
// TS2322: '{ name: string; }' is assignable to the constraint of type 'T',
// but 'T' could be instantiated with a different subtype of constraint.
// }
// Fix 1 — the shape is fixed, so say so.
function make(): { name: string } {
return { name: "anon" };
}
// Fix 2 — derive the result from a T the caller actually passed.
function stamp<T extends { name: string }>(x: T): T & { createdAt: number } {
return { ...x, createdAt: Date.now() };
}
// Fix 3 — let whoever chooses T also produce the value.
function makeWith<T extends { name: string }>(create: () => T): T {
return create();
}
const a = make();
const b = stamp({ name: "ada", id: 7 });
const c = makeWith(() => ({ name: "ada", id: 7 }));go deeper
Recall that the caller, not the function, decides what T is, so a value built inside the body cannot be assumed to match it. Recognise the error message when you see it.
Explain that the constraint is a lower bound and show the counterexample call — make<{ name: string; id: number }>() — that would receive an object missing id. Note the same error on assignment to a T-typed variable.
Diagnose the signature rather than the line: a type parameter appearing only in the return position has nothing to infer from. Present the concrete-return, derive-from-argument and caller-supplied-factory fixes and explain what as T really costs.
Set the policy for where unchecked assertions are allowed in shared code — typically only behind a validating boundary — and treat return-only type parameters in a published API as a defect, since they push unverifiable claims onto every consumer.
## Reading the error literally The message names both halves of the situation: ``` Type '{ name: string; }' is not assignable to type 'T'. '{ name: string; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ name: string; }'. ``` The first clause concedes your value fits the constraint. The second explains why that is not enough. ## Who chooses `T` A type parameter is chosen by the **caller**, at the call site, and the function body is checked once for every possible choice. The constraint sets a *lower bound*: `T` must be at least `{ name: string }`, and may be anything narrower. ```ts declare function make<T extends { name: string }>(): T; const u = make<{ name: string; id: number }>(); u.id.toFixed(); // the signature promised this exists ``` If the body were allowed to return `{ name: "anon" }`, that call would hand back an object with no `id`, and `u.id.toFixed()` would throw at runtime with no assertion anywhere in sight. The compiler is protecting the caller's side of a promise the signature made. The same reasoning explains a second, equally common form of the error — assigning to something *of* type `T`: ```ts function reset<T extends { a: number }>(x: T): T { // x = { a: 0 }; // Error TS2322: '{ a: number; }' is assignable to the constraint of type 'T', // but 'T' could be instantiated with a different subtype. return x; } ``` ## The structural diagnosis A type parameter that appears **only in the return position** is the shape that triggers this. There is nothing for inference to work from, so the caller must either annotate `T` explicitly or let it fall back to the constraint — and the body has no material to build a `T` out of. Whenever you hit this error, check first whether the generic is doing anything at all. ## Four ways out, in order of preference ### 1. Return the concrete type If the function really does build one fixed shape, say so: ```ts function make(): { name: string } { return { name: "anon" }; } ``` Callers who need a richer type now get an honest error at *their* site instead of a silent lie. ### 2. Derive the result from an argument Give the function a `T` to work with, and the type parameter starts carrying real information: ```ts function stamp<T extends { name: string }>(x: T): T & { createdAt: number } { return { ...x, createdAt: Date.now() }; } ``` Spreading a value of generic type produces `T & { createdAt: number }`, which *is* assignable to the declared return type, so this compiles with no assertion. ### 3. Let the caller supply the value A factory can take the construction step as a parameter: ```ts function makeWith<T extends { name: string }>(create: () => T): T { return create(); } ``` Whoever picks `T` is now also responsible for producing a matching value — which is exactly where that obligation belongs. ### 4. Assert, and own the consequence ```ts function makeUnsafe<T extends { name: string }>(): T { return { name: "anon" } as T; } ``` This compiles because `as` is an unchecked claim, not a conversion — it performs no runtime test and emits nothing. You have moved the unsoundness from a compile error in your file to a possible `undefined` in the caller's. It is defensible only behind a boundary that genuinely validates, and it deserves a comment saying why. ## Where the pattern legitimately appears The library shape it resembles is `JSON.parse` returning `any`, or a fetch wrapper declared `<T>(url: string): Promise<T>`. Those are the same unsound bargain wearing a nicer signature: the type parameter is a *request*, not a proof, and nothing checks the payload. That is a deliberate ergonomic tradeoff, and the honest version of it validates at the boundary — a parser or schema check that narrows to the claimed type — rather than asserting on faith. ## What to say in the interview Name the rule in one sentence — *the caller chooses `T`, so the body can consume a `T` but cannot produce one* — then show that you reach for a signature change before an assertion. Interviewers ask this because the reflexive fix is `as T`, and the difference between a candidate who knows why it compiles and one who thinks it fixed something is the whole point of the question.
- Why does the same function compile if you spread a parameter of type T into the returned object?Because you are deriving the result from a real `T` rather than inventing one. A spread of a generic value is typed `T & { extra: … }`, which is assignable to a declared `T & { extra: … }` return type. The caller's chosen members flow through instead of being fabricated.
- What exactly does `as T` change about the emitted JavaScript?Nothing. Assertions are erased with the rest of the type layer, so no check is inserted and no conversion happens. It only silences the checker, which is why an asserted return can hand a caller an object missing properties the signature promised.
- Is a fetch wrapper declared `<T>(url: string): Promise<T>` the same defect?It is the same unsound bargain, made deliberately. The type parameter is a caller's claim about the payload that nothing verifies, so a wrong claim surfaces as a runtime error far from the call. The disciplined version validates the response at the boundary and narrows to the claimed type instead.
saying these in an interview costs you the question
- Fixes it with as T and calls it solved
- Thinks satisfying the constraint means being T
- Believes the function gets to choose T
- Says the assertion adds a runtime check
- Adds a default type argument expecting the error to go away