keyof and indexed access types
keyof gives the union of a type's keys; T[K] indexes into a type. Index with a union of keys to get a union of value types, and Arr[number] to get an array's element type.
You define a User interface, then hand-write a second type UserKey = "id" | "name" | "email" for a function that picks a field. A teammate adds role to User and ships. Three months later a runtime bug: a config form lets users edit role, but a validation helper keyed off the stale UserKey silently skips it. The two definitions drifted because they were written twice. Index access types delete the duplicate: keyof User is the key union, derived automatically, and User[K] is the value type. Define the data once; let the types follow.
keyof: the union of a type’s keys
keyof T produces a union of the literal types of T’s property keys:
type User = { id: string; name: string; age: number };
type UserKey = keyof User;
// ^? type UserKey = "id" | "name" | "age"This union is derived — add a property to User and UserKey updates automatically. It is the building block for any operation that must iterate or constrain over a type’s own keys.
T[K]: indexed access
T[K] looks up the type stored at key K in T. The key must be a valid key of T (or you get an error):
type Name = User["name"];
// ^? type Name = string
type Age = User["age"];
// ^? type Age = number
type Bad = User["nope"];
// Error: Property 'nope' does not exist on type 'User'.Indexing is by type, not by value: User["name"] is using the string literal type "name" as the index, not a runtime string. This is why it works on types that have no runtime existence.
Indexing with a union of keys → union of value types
When you need to say “the value at any of these keys”, you do not write a separate union by hand — you index by the key union and let the checker derive it. This is where keyof and T[K] compose into a real single-source-of-truth tool.
Index with a union of keys and you get the union of the corresponding value types:
type IdOrAge = User["id" | "age"];
// ^? type IdOrAge = string | number
type AllValues = User[keyof User];
// ^? type AllValues = string | number ("id"→string, "name"→string, "age"→number)T[keyof T] — “index by every key” — is the idiom for “the union of all value types in T”. Duplicate value types collapse (string | string | number = string | number). This is a frequent senior tool: deriving an allowed-values union from a config object, or the payload-type union from a discriminated union’s members.
type Action =
| { type: "add"; n: number }
| { type: "clear" };
type ActionType = Action["type"];
// ^? type ActionType = "add" | "clear" — the tag union, derived from the variantsNotice this works straight on a union type: Action["type"] reads type from each member and unions the results. That keeps the discriminant tags from the previous lesson as a single source of truth.
number-indexing arrays and tuples → element type
Arrays and tuples are indexed by number. So T[number] is the element type:
type Names = string[];
type Element = Names[number];
// ^? type Element = string
const roles = ["admin", "editor", "viewer"] as const;
type Role = typeof roles[number];
// ^? type Role = "admin" | "editor" | "viewer"The as const + [number] combo is the canonical way to derive a literal union from a runtime array, so the array of values and its type stay in lockstep. For tuples, [number] gives the union of all positions, while a literal index gives one slot:
type Pair = [string, number];
type First = Pair[0];
// ^? type First = string
type Either = Pair[number];
// ^? type Either = string | numberkeyof is like asking a form which fields it has; T[K] is like asking what kind of value field K holds. Deriving types this way keeps one ___ of truth instead of two definitions that drift.
Pitfall: keyof on index-signature types
When a type has an index signature, keyof returns the signature key type, not a finite literal union:
type Dict = { [key: string]: number };
type K1 = keyof Dict;
// ^? type K1 = string | number
// (string index signatures also admit number keys, because JS coerces numeric keys to strings)
type NumDict = { [key: number]: boolean };
type K2 = keyof NumDict;
// ^? type K2 = numberThis surprises people: keyof a string-indexed type is string | number, not string, because a numeric property access like dict[0] is legal (JS turns it into dict["0"]). If you later try to constrain a generic K extends keyof Dict expecting precise keys, you instead get the broad string | number and lose the precision you wanted. Prefer a concrete object type (or Record<SpecificUnion, V>) when you need a finite key union.
▸Edge cases
Indexing with a literal versus a union changes precision dramatically. User["id"] is a single value type (string). User[keyof User] is the whole value-type union. And User[string] is an error unless User has a string index signature — indexing requires the index type to be assignable to a key of the target. This is why obj[someStringVariable] often errors on a precisely-typed object: a runtime string is wider than the object’s finite key union, so the access is unsound. Narrow the key to keyof T first.
Why this bridges to mapped types
Indexed access answers “what type is at this key”. The natural next question — “transform every key” — is mapped types (unit 04): { [K in keyof T]: ... } iterates the keyof T union and uses T[K] to read each value type. Everything you do with mapped types is built on the two operators in this lesson, so internalising keyof and T[K] now pays off directly there.
Derive instead of duplicate
1/3Given `type T = { a: string; b: number; c: string }`, what is `T[keyof T]`?
You have `const perms = ['read','write'] as const`. Which type gives you the union `'read' | 'write'`?
- 01What do keyof T and T[K] each produce, and why is indexing 'by type, not by value'?
- 02What does T[keyof T] give, and how does indexed access behave on a union and on arrays?
- 03Why does keyof on an index-signature type surprise you, and when should you avoid it?
Index access types close the unit by turning the union machinery into a way to derive types from data. keyof T gives the union of a type’s keys and T[K] reads the type at a key; indexing by a union of keys yields the union of value types, so T[keyof T] is every value type and Action["type"] extracts a discriminant tag union straight from the variants you modelled last lesson. Arrays index by number, making Arr[number] the element type and typeof arr[number] (with as const) a literal union pulled from a runtime array — the antidote to hand-written, drift-prone duplicate unions. The one pitfall to remember is that keyof on a string index signature is string | number, not string. These two operators — keyof and T[K] — are exactly the primitives that mapped types in unit 04 use to transform every key of a type at once, so the derivation skills you built here carry directly into the type-system-deep unit. Now when you see a hand-maintained key union drifting from its object, you will reach for keyof typeof instead of patching the duplicate.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.