Skip to content

keyof & Indexed Access

The keyof operator takes an object type and produces a union of its keys as a type.

interface User {
id: number;
name: string;
active: boolean;
}
type UserKey = keyof User;
// ^? "id" | "name" | "active"

keyof User is not a value — it is the union type "id" | "name" | "active". This is the raw material for iterating over a type, which is exactly what mapped types do.

For an object type with an index signature, keyof gives the index type:

type Dict = { [key: string]: number };
type DictKey = keyof Dict;
// ^? string | number (JS object keys can be accessed by number too)

Once you have keys, indexed access looks up the type of the value at a key — the type-level version of obj[key].

interface User {
id: number;
name: string;
}
type IdType = User["id"];
// ^? number
type NameType = User["name"];
// ^? string

You can index with a union of keys to get a union of value types:

type Values = User[keyof User];
// ^? number | string

User[keyof User] reads as “the type of every value in User” — a common idiom for getting the union of a type’s value types.

flowchart LR
  t["User { id: number, name: string }"] -->|keyof| keys["keys: id or name"]
  keys -->|"T[key]"| vals["value types: number or string"]
keyof gets keys; indexed access gets value types

Arrays and tuples are object types too, so indexed access works on them — and [number] is the trick for “the element type”:

type Nums = number[];
type Elem = Nums[number];
// ^? number
const tuple = [1, "two", true] as const;
type TupleElem = (typeof tuple)[number];
// ^? 1 | "two" | true
type First = (typeof tuple)[0];
// ^? 1

Arr[number] means “index this array with any number,” which yields the union of all element types — the standard way to turn an array/tuple type into a union.

Nearly every utility type is built from keyof plus indexed access plus a mapped type:

// Pick, from scratch — iterate chosen keys, look up each value type
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type NameOnly = MyPick<User, "name">;
// ^? { name: string }

K extends keyof T constrains the keys to real keys of T; [P in K] iterates them (a mapped type, next lesson if you skipped it); T[P] looks up each value type. Three primitives, one useful tool.

What does `keyof User` produce for `interface User { id: number; name: string }`?
What does the indexed access type `User["name"]` give you?
How do you get the element type of an array type `T = string[]`?
What does `User[keyof User]` represent?