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

    ↑↓ move · ⏎ open · esc close

    Concept 3 of 12

    keyof and indexed access

    These two operators are how you take an object type apart. `keyof` gets you the keys, indexed access gets you a value type back. Almost every useful transformation in this track is those two combined with something that iterates.

    keyof gives a union, not a list

    interface User { id: number; name: string }
    
    type Keys = keyof User        // 'id' | 'name'
    type Values = User[keyof User] // number | string

    The result is a union of literal key types, in no particular order. There is no way to ask for "the first key" — order is not part of what an object type guarantees, and anything relying on it would be relying on an implementation detail.

    Indexing with a union distributes

    interface User { id: number; name: string; admin: boolean }
    
    type One = User['id']              // number
    type Some = User['id' | 'name']    // number | string
    type Every = User[keyof User]      // number | string | boolean
    
    type Element = string[][number]    // string

    Indexing with a union of keys yields the union of the corresponding value types. `T[number]` on an array or tuple is the same idea and is how you turn a tuple into a union in one step.

    The common wrong answer

    interface User { id: number; name: string }
    
    // Intent: the value types of User.
    type Values = keyof User[]
    // Actually parses as keyof (User[]) — the keys of an ARRAY:
    // 'length' | 'push' | 'pop' | ...
    
    type Fixed = User[keyof User]  // number | string

    Postfix `[]` binds tighter than `keyof`, so this asks for the keys of an array of users rather than the values of a user. The result is a large union of array method names, which then fails somewhere far from the actual error.

    Exercise

    Produce the union of every value type in `T`.

    Try it

    1
    type User = { id: number; name: string }
    • Values<User>
      string | number

    Takeaway

    `keyof T` opens an object up and `T[K]` reads out of it. Reach for these before writing a recursive type — a surprising number of problems need nothing more.