Constructs
Conditional types
The type-level `if`. The question it asks is always assignability.
What it is
Not equality, not instance-of — assignability, in one direction. And when the checked type is a bare type parameter, the conditional distributes: it runs once per union member and the results are unioned back. That single behaviour explains most of what looks like magic in the standard library, and most of what looks like a bug in your own types.
Examples
IsString<'hello'>→trueIsString<string | number>
→booleanIt distributed: `true` for `string`, `false` for `number`, unioned back into `boolean`. Surprising until you know, obvious afterwards.
NoDistribute<string | number>
→falseThe tuple wrapper turns distribution off, and now the union is judged as one thing.
Each resolved type above was printed by TypeScript 5.9.3, not written by hand.
What it does not do
- It does not test equality. `T extends string` is true for every string literal too, and asking "are these the same type" needs the two-conditional identity trick, not this.
- It does not distribute when the checked type is anything but a bare parameter. Wrapping either side — in a tuple, an object, anything — switches it off.
Takeaway
When a conditional gives an answer you did not expect, ask first whether it distributed. `[T] extends [U]` is the switch, and it is the fix about half the time.