NoInfer<Type>
Blocks a type parameter from being inferred at that position so another argument decides it.
type NoInfer<T> = intrinsic;- Added in TypeScript 5.4
Parameters
| Parameter | Type | Description |
|---|---|---|
$T | any type | The type to pass through unchanged while marking its position as non-inferring. |
Return value
T`T` itself. The only difference is that the compiler will not use this position as a source of inference for `T`.
Errors
Nothing itself, but blocking the inference site is what makes an otherwise-accepted argument a compile error — that is the point of using it.
Changelog
- PHP 5.4Added as a compiler intrinsic, replacing hand-rolled community workarounds such as `[T][0]` and `T & {}`.
TypeScript infers a generic parameter from every position it appears in, then takes the union of the candidates. That is usually what you want, and occasionally a disaster. In createStreetLight(colors: C[], defaultColor?: C), passing ["red", "green"] and "blue" does not fail — the compiler happily widens C to "red" | "green" | "blue" and everything type-checks, because the second argument was allowed to vote.
Wrapping the second parameter as NoInfer<C> removes its vote. C is now fixed by the array alone, and "blue" is checked against "red" | "green" and rejected.
When to reach for it
- A default or fallback value that must be one of the options listed elsewhere in the same call.
- A callback parameter that should be checked against the state type rather than redefine it.
- Any API where one argument is the source of truth and the others should merely conform.
Everywhere else it is a no-op: NoInfer<T> is assignable to and from T, has no runtime representation, and does not change the resulting type in the slightest. Before 5.4 people emulated it with tricks like [T][0] or intersecting with {}; the intrinsic version is more reliable and reads as intent.
Example
function createStreetLight<C extends string>(
colors: C[],
defaultColor?: NoInfer<C>,
) {
return { colors, active: defaultColor ?? colors[0] };
}
console.log(createStreetLight(["red", "yellow", "green"], "red"));
// createStreetLight(["red", "yellow", "green"], "blue");
// Argument of type '"blue"' is not assignable to parameter
// of type '"red" | "yellow" | "green"'.
//
// Without NoInfer, C would widen to include "blue" and this would compile.When to use it
- Force a `defaultValue` argument to be one of the members of an options array passed in the same call.
- Keep a reducer's initial state from widening the state generic away from the one the reducer declares.
- Make a `key` argument conform to an object's keys instead of extending them.
Frequently asked questions
How do I stop TypeScript from inferring a generic from one particular argument?
Wrap that parameter's type in `NoInfer<...>`, available in TypeScript 5.4 and later. The parameter is still checked against the generic, it just no longer contributes a candidate when the compiler decides what the generic is.
What did people use before NoInfer existed?
Community workarounds such as `[T][0]`, `T & {}`, or a helper interface with a self-referential lookup. They mostly worked but were fragile across compiler versions; the intrinsic `NoInfer` is supported directly by the inference algorithm.
Does NoInfer change the type at runtime or in the emitted output?
No. `NoInfer<T>` is identical to `T` for assignability and is erased entirely during compilation, so the JavaScript emitted is byte-for-byte the same with or without it.
More examples
Before and after: why the error appears
Both functions have identical bodies and identical runtime output (`blue green`); only the type checking differs. The first silently expands the union to accommodate a colour the street light does not have, the second reports it at the call site.
// Without NoInfer — "blue" votes on C, so nothing is rejected.
function loose<C extends string>(colors: C[], fallback?: C) {
return fallback ?? colors[0];
}
const a = loose(["red", "green"], "blue");
// C inferred as "red" | "green" | "blue" — compiles, bug ships.
// With NoInfer — C is decided by the array alone.
function strict<C extends string>(colors: C[], fallback?: NoInfer<C>) {
return fallback ?? colors[0];
}
const b = strict(["red", "green"], "green"); // OK
// strict(["red", "green"], "blue");
// Error: '"blue"' is not assignable to '"red" | "green"'.
console.log(a, b);Pinning state in a reducer-style API
`S` is fixed by `initial`, so the reducer's `s` parameter is contextually typed as `{ count: number; label: string }` and needs no annotation. Without `NoInfer`, an inconsistent reducer would quietly widen `S` into a union instead of reporting a mismatch. Running it would print `{ count: 2, label: 'clicks' }`.
type Reducer<S> = (state: S, action: string) => S;
function createStore<S>(initial: S, reducer: NoInfer<Reducer<S>>) {
let state = initial;
return {
dispatch(action: string) { state = reducer(state, action); return state; },
get: () => state,
};
}
const store = createStore(
{ count: 0, label: "clicks" },
(s, action) => (action === "inc" ? { ...s, count: s.count + 1 } : s),
);
store.dispatch("inc");
store.dispatch("inc");
console.log(store.get());
Discussion