Defaults, const Type Parameters & NoInfer
Intermediate · 20 min read · ▶ live playground · ✦ checkpoint
Default type parameters, const type parameters, and NoInfer<T> are not three unrelated tricks. They all control the same process from 7.1: the checker solves for T from evidence, and sometimes you need to guide where that evidence comes from.
Defaults: when there is nothing to solve from
A type parameter can have a default:
type Field<T = string> = {
name: string;
value: T;
};Read it out loud: "Field, for some type T, defaulting to string." The default matters only when the caller leaves T out and inference has no better evidence. If the caller supplies Field<boolean>, the default is ignored.
Field without a type argument means Field<string>. emptyList() returns string[] for the same reason: there is no value argument for the checker to inspect, so the default supplies the answer. emptyList<number>() still works because an explicit type argument is stronger than the default.
Defaults are common in generic aliases and classes where one case is overwhelmingly normal. They are not a replacement for inference; they are the fallback when inference has no evidence.
const type parameters: keep literal evidence
You already know const variables keep literal types more precisely than let variables. A const type parameter brings that same preference to a generic call: keep the literal shape that came in, instead of widening it too early.
The important result is statuses[0]. The checker knows it is exactly "draft", so it can pass into firstMustBeDraft. Without preserving the literal tuple through the call, the array would quickly become "some string array," and that precise first-position fact would be gone.
const on a type parameter is not a runtime feature. It does not create a new kind of JavaScript value. It tells inference to keep literal and tuple information when solving T for this call.
NoInfer: stop the wrong argument from voting
Sometimes inference listens to too many arguments. Suppose you have a list of allowed choices and a fallback. You want the list to decide the allowed type; the fallback should be checked against that list, not expand it.
chooseLoose accepts "system" because both arguments help solve Choice, so the checker can choose "light" | "dark" | "system". That is not the relationship you meant. chooseStrict uses NoInfer<Choice> on the fallback, so Choice is solved from the choices array first, then the fallback is checked against that solved type.
Use NoInfer<T> rarely and locally. If the function signature reads clearly without it, leave it alone. Reach for it when you can name the problem precisely: "this parameter should be checked against T, but it should not help solve T."
Variance, one paragraph
Variance asks how a generic type changes when its type parameter changes. If one type is assignable to another, should Box<First> be assignable to Box<Second> too? The answer depends on where T appears. If T only comes out of a value, widening is usually safe. If T goes into a value, the same move can let the wrong item enter. If T goes both in and out, the checker has to be stricter. That is enough for now; Part VI gives the real vocabulary and edge cases.
Next lesson closes the section by naming the patterns that make generics feel harder than they are: type parameters used only once, pretending you can produce a T from nothing, and choosing a generic when a union or overload would read better.
Checkpoint
Answer all three to mark this lesson complete