Skip to content
webtype.orgwebtype.orgTwelve concepts · 2/12

    ↑↓ move · ⏎ open · esc close

    Concept 2 of 12

    Generic parameters

    A generic parameter turns an alias into a function that takes types and returns a type. Once you read it that way — arguments in, type out — the rest of the type system stops looking like syntax and starts looking like programming.

    Parameters, defaults and all

    type Box<T> = { value: T }
    type Boxed = Box<string>        // { value: string }
    
    // Defaults work exactly like function parameter defaults.
    type List<T, Sep extends string = ','> = { items: T[]; sep: Sep }
    type Plain = List<number>       // sep is ','

    Type parameters are positional, can have defaults, and the defaults may reference earlier parameters. Recursive type-level code leans on this constantly: an accumulator is nearly always a defaulted parameter the caller never passes.

    `extends` here means "must be at least"

    type Length<S extends string> = S['length']
    
    Length<'abc'>   // ok
    Length<42>      // Error: 42 does not satisfy the constraint 'string'

    In a parameter list, `extends` is a constraint: it restricts what may be passed. It is not inheritance, and it does not mean the parameter *is* that type — only that whatever arrives is assignable to it.

    The same keyword means something completely different inside a conditional type, where it asks a question rather than imposing a rule. Telling the two apart by position is one of the genuine hurdles in learning this.

    The common wrong answer

    // Intent: accept any object and read a key from it.
    type Get<T, K> = T[K]
    //                 ^ Error: type K cannot be used to index type T
    
    // Fixed: constrain K to the keys T actually has.
    type Get2<T, K extends keyof T> = T[K]

    Without a constraint the compiler has no reason to believe `K` is a valid key, so it refuses. Constraints are not decoration — they are what lets the body of a generic type do anything with its parameters.

    Exercise

    Make `Box` take a type parameter, so `Box<string>` holds a string.

    Try it

    2
    • Box<string>
      { value: string }
    • Box<1>
      { value: 1 }

    Takeaway

    Read `type F<A, B> = ...` as a function from types to types. `extends` in the parameter list is the argument check at the door.