In TypeScript, what rules govern default type parameters — where a default may appear in the list, what type it may reference, and how it interacts with an `extends` constraint on the same parameter?
answer
- optional entries must be trailing
- a default can look left
- forward references are rejected
- constraint first, then default
- the default must satisfy the constraint
basics
~20 sDefaults must come last: once a type parameter has one, every later parameter needs one too. A default may reference parameters declared before it but not after, and when the parameter is also constrained the default must itself satisfy that constraint.
solid answer
~50 sThree rules. First, position: a required type parameter may not follow a defaulted one, so defaults only ever sit at the tail of the list — that is what makes the short reference form work, since only trailing arguments can be omitted. Second, scope: a default may refer to type parameters declared earlier in the same list, as in `<Item, Page = Item[]>`, but not to later ones; referring forward is rejected. Third, constraints: the syntax order is constraint then default, `<T extends X = D>`, and the compiler checks that `D` satisfies `X` — `<T extends string = number>` fails because the default violates the constraint. The common idiom `<T extends object = object>` just makes the constraint double as the default. Together these determine the legal arity of a reference: with one required and one defaulted parameter, a reference may supply one or two arguments.
code
typescript · 15 lines// A default may reference a parameter declared before it.
interface Pageable<Item, Page = Item[]> {
current: Page;
}
type A = Pageable<string>; // Page = string[]
type B = Pageable<string, Set<string>>; // both supplied
// Constraint first, then a default that satisfies it.
type Ids<T extends string = string> = T[];
// Each of these declarations is rejected:
// interface Bad1<A = string, B> {} // required after optional
// interface Bad2<A = B, B = string> {} // default references a later parameter
// type Bad3<T extends string = number> = T[]; // default violates the constraintgo deeper
Recall that defaults go at the end of the list and that the syntax is the parameter, then any constraint, then the default value. Be able to write one correctly on a two-parameter generic.
Explain all three rules and why the position rule follows from positional type-argument lists. Show a default that references an earlier parameter, and state that a default must satisfy its own constraint or the declaration itself fails.
Demonstrate judgment about which parameters deserve defaults and which derivation to default to, since a derived default like Page = Item[] keeps the common reference short without hiding the override. Read the compiler's arity range back to a colleague who is confused about how many arguments a generic wants.
Set the convention: how many type parameters a public generic may carry, when descriptive T-prefixed names replace single letters, and whether constraint-as-default is the house norm. These choices persist for the life of the type because every reference site depends on the order.
## Position: defaults live at the tail A type parameter with a default is optional, and an optional parameter cannot be followed by a required one. `interface Bad<A = string, B> {}` is rejected — the compiler's complaint is that required type parameters may not follow optional type parameters. Once you default any parameter, everything to its right must be defaulted too. This is not an arbitrary restriction; it falls directly out of how references work. Type-argument lists are positional and there is no placeholder for a skipped entry, so the only arguments a caller can omit are the trailing ones. A default on a middle parameter could never actually be used without also defaulting everything after it, so the language forbids the shape outright rather than letting you write something useless. ## Scope: a default may look left, never right The default is a type expression evaluated in the scope of the parameter list, and it can name parameters that were declared before it: ```ts interface Pageable<Item, Page = Item[]> { current: Page; } type A = Pageable<string>; // Page = string[] type B = Pageable<string, Set<string>>; ``` This is genuinely useful: the second parameter usually derives from the first, and defaulting it to that derivation keeps the common reference short while still allowing an override. Referring *forward* is an error — `interface Bad<A = B, B = string> {}` is rejected because a default may only reference previously declared type parameters. Self-reference is likewise circular and rejected. ## Constraints: order, and the satisfaction check When a parameter carries both a constraint and a default, the constraint is written first: ```ts type Ok<T extends string = string> = T[]; // type Bad<T extends string = number> = T[]; // default does not satisfy the constraint ``` The compiler checks the default against the constraint at declaration time, so a default that violates its constraint is an error on the declaration, not on some later use. The idiom `<T extends X = X>` — repeating the constraint as the default — is extremely common, and it says something precise: callers may narrow this parameter, and if they say nothing they get the widest type the constraint allows. It is the honest default for a parameter whose whole purpose is narrowing. A subtlety worth knowing: the constraint governs what a caller may *supply*, while the default governs what they *get* if they supply nothing. They are checked independently, which is why the satisfaction rule has to exist at all. The constraint mechanism itself — what `extends` means for a type parameter, what constraints buy you, how they shape inference — is a separate topic; here the only thing that matters is how a constraint and a default coexist on one parameter. ## What the rules add up to: legal arity The declaration decides the range of type arguments a reference may supply. With `interface Envelope<Payload, Meta = string>`, one parameter is required and one is optional, so a reference may pass one or two arguments; the compiler's error for a bad count phrases it as exactly that range. With no defaults at all, the count is exact. This is the practical, user-visible consequence of the position rule: the number of defaults is the size of the window. ## Naming, briefly The conventional single letters — `T`, then `U` and `V` for successive parameters, `K` and `V` for a key/value pair, `E` for an element — read fine on a one- or two-parameter generic where the reader can hold the whole signature in view. Past two parameters they stop helping, because a reference site shows only positions and the reader has to count. Library code therefore tends to use descriptive names with a `T` prefix, such as `TData` and `TError`, so that the declaration is self-documenting even though the call site is still positional. That is convention, not a compiler rule; the checker accepts any identifier. ## Erasure None of this survives compilation. Defaults, constraints and parameter names are all resolved during checking and then erased; the emitted JavaScript has no representation of a type parameter, so no rule here has any runtime cost or runtime meaning.
- Why does the language forbid a required type parameter after a defaulted one instead of just ignoring the default?Because type-argument lists are positional with no placeholder for a skipped entry, so only trailing arguments can ever be omitted. A default on a middle parameter could never be exercised while a later parameter was still required — the caller would have to supply it anyway. Rejecting the declaration surfaces the mistake where it is, rather than at every confused call site.
- What does the pattern `<T extends X = X>` communicate to a reader?That the parameter exists so callers can narrow, and that omitting it yields the widest type the constraint permits. The constraint bounds what may be supplied; the default supplies the bound itself when nothing is said. It is the honest neutral choice, as opposed to picking some arbitrary member of the constrained set as the default.
- Can a default reference the parameter that follows it in the list?No. A default may only reference type parameters declared earlier in the same list, so `<Item, Page = Item[]>` is fine while `<Page = Item[], Item>` is doubly wrong — it references a later parameter and it also puts a required parameter after an optional one.
saying these in an interview costs you the question
- Thinks a default can sit anywhere in the parameter list
- Assumes a default overrides or replaces the constraint
- Writes the default before the extends clause
- Believes a default may reference a later type parameter
- Says a defaulted parameter no longer counts toward the type-argument count