Lesson 35·M4 Type System Fluency
keyof, typeof & Indexed Access
OpenLearning Objectives
By the end of this lesson you should be able to:
- Explain what each of
keyof,typeofand indexed access does in one sentence - Say which operator operates on values and which operates on types
- Use indexed access
T[K]to read a property type and to look up a tuple element - Combine all three to type the output of a function or the keys of a config
- Diagnose the two most common operator mix-ups
One-line definition
keyof T reads the keys of a type, typeof v reads the type of a value, and T[K] reads the type of a property — the three moves for "derive a type from something that already exists".
typeof is the only one that works on values; the other two work on types.
Mental model
the VALUE world the TYPE world
──────────────── ───────────────
const user = { type User = {
id: 1, id: number;
name: 'Ali', name: string;
} }
│ │
│ typeof user │ keyof User
▼ ▼
type of the value: union of the keys:
{ id: number; 'id' | 'name'
name: string }
typeof reaches from the value world into the type world; keyof works entirely inside the type world. T[K] reads inside a type — User['name'] is string.
Visual flow
value: const user = { id: 1, name: 'Ali' }
│ typeof
▼
type: type T = { id: number; name: string }
│ keyof
▼
keys: 'id' | 'name'
│ indexed access T[K]
▼
prop: T['name'] = string
One value, three derivations — and each derivation is the inverse of the next: typeof goes value→type, keyof goes type→keys, T[K] goes type+key→property type.
How it works
typeof v — take a value, get its type:
const config = { url: '/api', retries: 3 };
type Config = typeof config; // { url: string; retries: number }
const point = { x: 1, y: 2 };
type Pt = typeof point; // { x: number; y: number }
typeof works on any value expression — a variable, a function, an array. It does not run anything; it just reads the type of what's there.
keyof T — take a type, get the union of its keys:
type User = { id: number; name: string; email: string };
type UserKey = keyof User; // 'id' | 'name' | 'email'
const key: UserKey = 'name'; // ✅ any single key
For a union of objects, keyof gives the keys common to every member:
type A = { a: number; shared: string };
type B = { b: number; shared: string };
type K = keyof (A | B); // 'shared' — the only key both have
Indexed access T[K] — take a type and a key, get the property's type:
type User = { id: number; name: string };
type Id = User['id']; // number
type Any = User['id' | 'name']; // number | string — union of property types
type All = User[keyof User]; // number | string — the value union of the object
// tuples: index a tuple by a literal to get the element type
type Tuple = [string, number, boolean];
type Second = Tuple[1]; // number
type Head = Tuple[0]; // string
type Elems = Tuple[number]; // string | number | boolean — every element
Indexed access is the "look inside" operator: it's how every type-transforming type (Pick, mapped types, ReturnType) reads properties. Lesson 39 builds on exactly this.
Real project usage
Typing an object you only have as a value — the typeof pattern:
const theme = { dark: true, accent: '#0ff', radius: 4 } as const;
type Theme = typeof theme;
// { readonly dark: true; readonly accent: '#0ff'; readonly radius: 4 }
A config whose keys must match an object's keys — keyof:
type User = { id: number; name: string };
const defaults: Record<keyof User, unknown> = { id: 0, name: '' };
A lookup function typed by the object's own keys — all three together:
const users = { 1: 'Ali', 2: 'Omar' };
type Id = keyof typeof users; // '1' | '2'
function nameFor(id: Id): string {
return users[id]; // ✅ only valid keys compile
}
console.log(nameFor('1'));
// nameFor(1); // ❌ number isn't '1' | '2'
Output:
Ali
keyof typeof users is the pattern for "the type of an object's keys" — you'll meet it constantly in real codebases and in Lesson 39's utility types.
Interview explanation
typeoftakes a value and gives its type —typeof configbecomes the type of the config object.keyoftakes a type and gives the union of its keys —keyof Useris'id' | 'name'. Indexed access reads a property's type:User['id']isnumber. Together they let me derive types from existing values and shapes instead of repeating them by hand.
Senior-level insights
The junior answer defines the three operators. The senior answer treats them as one composition:
- The canonical recipe is
keyof typeof obj— "the keys of the object I already have" — andT[K]for "the type stored at that key". - With
keyofand indexed access you can express the whole family of type transformations without any magic:Pick<T, K>is{ [P in K]: T[P] },Partial<T>is{ [P in keyof T]?: T[P] }. The built-in utilities are just these operators, wrapped. (Lesson 39 covers them.) - A senior knows
keyofon a union is the intersection of keys, andkeyofon an array isnumber | 'length' | …— the array's own members, not its element type. - When the object is
as const,typeofreads the literal values, not just the broad types — which is whykeyof typeofon aconstmap gives you exact literal keys.
Common mistakes
Mistake 1 — using typeof on a type. It's a value operator:
type User = { id: number };
type Bad = typeof User; // ❌ 'User' only refers to a type
error TS2693: 'User' only refers to a type, but is being used as a value here.
typeof needs a value on the right. For a type you want keyof or indexed access, not typeof.
Mistake 2 — forgetting typeof in keyof typeof. keyof users on a value is an error — keyof needs a type. The fix is almost always keyof typeof users.
Mistake 3 — expecting T[keyof T] to be the keys. It's the value union — all the property types joined. Keys are keyof T; values are T[keyof T].
Best practices
✅ Reach for keyof typeof obj to type "keys of this object I already have"
✅ Use T[K] to read a property's type instead of re-declaring it
✅ Pair keyof with as const for exact literal keys
✅ Combine all three: keyof typeof for keys, T[K] for values, typeof for value→type
❌ Don't use typeof on a type, or keyof on a value
❌ Don't duplicate a shape you can derive with typeof value or T[K]
Interview questions
Q1. What is the difference between keyof and typeof?
typeofworks on values and produces their type —typeof configis the type of theconfigobject.keyofworks on types and produces the union of their keys —keyof Useris'id' | 'name'. One moves from the value world to the type world; the other stays inside the type world.
Q2. What is indexed access in TypeScript?
The
T[K]syntax: it reads the type of a property.User['id']isnumber. With a union of keys it gives a union of property types, and withkeyofit gives the whole value union of an object. It's the lookup operator the utility types are built on.
Q3. How would you type the keys of an object you already have?
keyof typeof obj. Thetypeofturns the value into a type, andkeyofreads its keys. With aconstobject annotatedas const, that gives the exact literal keys rather than the broadstring.
Senior follow-up: How do keyof and indexed access relate to the built-in utility types?
They're the implementation.
Pick<T, K>is a mapped type over the chosen keys:{ [P in K]: T[P] }— indexed access reading each chosen key's type.Partial<T>maps over every key with a?:{ [P in keyof T]?: T[P] }. So the utilities are literallykeyofplus indexed access wrapped in a generic. Learning the operators first is what makes the utilities obvious rather than magic.
Follow-up questions
What does keyof give you for a union type?
Only the keys that exist on every member — the intersection.
keyof (A | B)whereA = { a, shared }andB = { b, shared }is just'shared'. If you want the keys of any member, you need a mapped type over the union (Lesson 41).
Can I index into an array or tuple?
Yes. A tuple with a literal index gives the element type at that position —
Tuple[1]isnumber.Tuple[number]gives the union of every element type. For a plain array,T[number]is the element type — that's howArray<T>exposes it.
Does typeof run the value?
No. It's purely compile-time — it reads the type of the expression without executing it.
typeofin TypeScript is the type operator, not the JavaScript runtimetypeofthat returns'string','object'and friends. Different feature, same keyword.
Comparison table
| Operator | Operates on | Produces | Example | Result |
|---|---|---|---|---|
typeof | a value | a type | typeof config | { url: string; retries: number } |
keyof | a type | union of keys | keyof User | 'id' | 'name' |
| indexed access | a type + key(s) | a property type | User['id'] | number |
keyof typeof | a value | union of keys | keyof typeof users | '1' | '2' |
Code example
Deriving an entire API client's types from a single config object:
const endpoints = {
users: '/users',
posts: '/posts',
comments: '/comments',
} as const;
type Endpoint = keyof typeof endpoints; // 'users' | 'posts' | 'comments'
type Path = typeof endpoints[Endpoint]; // '/users' | '/posts' | '/comments'
function buildUrl(e: Endpoint): string {
return `https://api.example.com${endpoints[e]}`;
}
console.log(buildUrl('users'));
console.log(buildUrl('posts'));
Output:
https://api.example.com/users
https://api.example.com/posts
buildUrl('nope') is a compile error — Endpoint only admits the three real routes. Two type lines derived the whole contract from one value.
Performance notes
All three operators are erased at runtime — zero runtime cost, pure compile-time reading. The only performance story is compile time, and it's negligible for these: keyof and indexed access on ordinary objects resolve instantly. They only start to matter at all when you nest them dozens of levels deep in pathological mapped types — and even then the cost is a few milliseconds of checking, not a runtime concern.
Debugging scenarios
"'X' only refers to a type, but is being used as a value here." You wrote typeof User where User is a type. Either remove typeof (and use keyof / indexed access) or point it at an actual value.
"'users' refers to a value, but is being used as a type here." You wrote keyof users — keyof needs a type. Add typeof: keyof typeof users.
"Property 'x' does not exist on type '…'." Your T[K] used a key that isn't on the type, or keyof produced a narrower union than you assumed. Check whether the type is a union (whose keyof is the intersection) or a plain object.
"'string' is not assignable to type 'never'." An indexed access over an empty or impossible key set. Often the key union was accidentally emptied — e.g. keyof on a union of types with no common keys gives never.
Quick revision notes
typeof= value → type (compile-time only, never runs the value)keyof= type → union of keys; on a union of objects → only the common keysT[K]= type + key → property type;T[keyof T]= all property typesT[number]on an array/tuple = element types- The canonical combo:
keyof typeof obj— keys of an object you already have - Pair
as constwithtypeofto read exact literals - The utility types in Lesson 39 are built from
keyof+ indexed access
Cheat sheet
const config = { url: '/api', retries: 3 };
type C = typeof config; // { url: string; retries: number }
type User = { id: number; name: string };
type K = keyof User; // 'id' | 'name'
type Id = User['id']; // number
type V = User[keyof User]; // number | string
type Tup = [string, number];
type Second = Tup[1]; // number
type Elems = Tup[number]; // string | number
const obj = { a: 1, b: 2 } as const;
type OKeys = keyof typeof obj; // 'a' | 'b'
(no runtime output — these are type-level definitions)Key takeaways
Recap
typeofmoves from a value to its type;keyofmoves from a type to its keys;T[K]reads a property's typekeyof typeof objis the daily pattern for "keys of this object"T[keyof T]is the value union of an object;T[number]is the element type of an array or tuplekeyofon a union of objects gives only the common keys- Pair with
as constto preserve exact literal types - All three are compile-time only — zero runtime cost
- These operators are the implementation of the utility types in Lesson 39
- Next stop: Lesson 36 uses them everywhere once you start writing generics
Check your understanding
Answer each one out loud. Then rate yourself honestly.
0/6Which operator works on values, and which work on types?
What does keyof typeof users produce, and why do you need both halves?
What is User['id' | 'name']? What is User[keyof User]?
How do you read the element type of an array, and of a tuple position?
What does keyof return for a union of two different object types?
Why does as const change what typeof gives you?
What's Next
Lesson 36 — Generics. The second-most-asked TypeScript topic. Generics are functions over types — and keyof and indexed access are exactly the tools a generic body needs.