Preventing Distributive Type Mapping in Conditionals
Conditional types can be useful for narrowing a type parameter. However, when the checked type is a pure type reference, TypeScript "recalculates" the type in all subsequent usage (both the true and false cases). This can be useful in some cases where different types in a single union apply to multiple conditionals.
However, this behavior of distributing a union type over a generic type can lead to some confusing behavior.
type Transform<T> = (x: T) => T; type StringTransform1<T> = T extends string ? Transform<T> : Transform<unknown>; type StringTransform2<T> = [T] extends [string] ? Transform<T> : Transform<unknown>; declare const transform1: StringTransform1<"a" | "b">; transform1("a"); declare const transform2: StringTransform2<"a" | "b">; transform2("a");
Due to the distribution in StringTransform1 it produces the type Transform | Transform. In TypeScript, parameters are contravariant which means that this computed union flattens to (x: never) => "a" | "b" which is not particularly useful.
To circumvent this behavior, the checked type in the conditional can be a type constructed from the reference. The simple example used here is a one-element tuple, that replaces both the check and extends types. The conditional check should function the same, but it now prevents TypeScript from narrowing the type reference in subsequent cases.










