Skip to content
webtype.orgwebtype.org

    ↑↓ move · ⏎ open · esc close

    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.

    Follow the typeCompiler checked
    ExpressionIsText<"hello">
    Resolves totrue
    ExpressionIsText<42>
    Resolves tofalse

    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 : false

    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.

    Exercise

    Answer true when T is a string, false otherwise.

    Try it

    2
    • IsString<'a'>—
      → true
    • IsString<number>—
      → false

    Takeaway

    Conditionals ask about assignability and test branches in order. Narrow first, and reach for Equal the moment you need actual sameness.

    Practiced in