NoInfer<Type>

Blocks a type parameter from being inferred at that position so another argument decides it.

Signature
type NoInfer<T> = intrinsic;
  • Added in TypeScript 5.4

Parameters

ParameterTypeDescription
$Tany typeThe 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

Example · typescript
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.

Example · typescript
// 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' }`.

Example · typescript
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

  • Be the first to comment on this lesson.