Concept 6 of 12
Conditional types
A conditional type is the type-level `if`. The question it asks is always the same one: is the left side assignable to the right? Not "are these equal", not "is this an instance of" — assignable, one direction only.
Assignability, not equality
type A = 'hello' extends string ? true : false // true type B = string extends 'hello' ? true : false // false type C = never extends string ? true : false // true — never fits anywhere type D = { a: 1; b: 2 } extends { a: 1 } ? true : false // true
A narrower type is assignable to a wider one, so the test is directional. An object with extra properties is assignable to one with fewer, which is why `extends` cannot be used to check that two object types are the same shape.
When you genuinely need "the same type", you need the `Equal` helper every check on this site is built on — two deferred conditionals compared for identity, which is stricter than assignability in both directions.
Branches are tested in order
type Describe<T> = T extends (...args: never[]) => unknown ? 'function' : T extends unknown[] ? 'array' : T extends object ? 'object' : 'primitive'
Narrower cases have to come first. Functions and arrays are both objects, so testing `extends object` early would swallow them — the classic cause of a `DeepReadonly` that quietly destroys every function it touches.
The common wrong answer
// Intent: is T exactly string? type IsString<T> = T extends string ? true : false IsString<'hello'> // true — but 'hello' is not string, it is narrower IsString<any> // boolean — any matches both branches at once
`extends` answers "fits into", so every string literal passes a test for `string`. And `any` is assignable to everything, so a conditional given `any` returns the union of both branches — a result that is almost never what the author intended.
Takeaway
Conditionals ask about assignability and test branches in order. Narrow first, and reach for `Equal` the moment you need actual sameness.