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.
- Expression
IsText<"hello"> - Resolves to
true - Expression
IsText<42> - Resolves to
false
extends asks about assignability. A string literal takes the true branch; a number takes the false branch.
Type definition
type IsText<T> = T extends string ? true : falseAssignability, 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.
Exercise
Answer true when T is a string, false otherwise.
Try it
2IsString<'a'>—→ trueIsString<number>—→ false
Takeaway
Conditionals ask about assignability and test branches in order. Narrow first, and reach for Equal the moment you need actual sameness.

