Skip to content
webtype.orgTwelve concepts · 3/12

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.

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.