keyof, typeof and Indexed Access Types

Intermediate
11 min

keyof, typeof and Indexed Access Types

Large codebases repeat themselves when types are written by hand next to the values they describe: an object literal here, an interface for it there, a union of its keys somewhere else. TypeScript's type operators let you derive one from another so there is a single source of truth. This lesson covers keyof, the type-level typeof, and indexed access types, and shows the patterns that combine them.

keyof: The Keys of a Type

keyof T produces a union of the property names of T as literal types:

typescript
interface Person { name: string; age: number; email?: string } type PersonKey = keyof Person; // "name" | "age" | "email" function printField(p: Person, key: PersonKey) { console.log(key, p[key]); } printField({ name: "Ada", age: 36 }, "age"); // OK printField({ name: "Ada", age: 36 }, "phone"); // Error

Details worth knowing:

  • Optional properties are included; keyof describes names, not presence.
  • For a string index signature, keyof is string | number, because JavaScript converts numeric keys to strings.
  • Use Extract<keyof T, string> when you need only string keys (for example to build a template literal).

typeof in Type Positions

JavaScript's typeof returns a string at runtime. In a type position, TypeScript's typeof captures the static type of a variable, property or function:

typescript
const defaults = { retries: 3, timeout: 5000, verbose: false }; type Options = typeof defaults; // { retries: number; timeout: number; verbose: boolean } function merge(overrides: Partial<Options>): Options { return { ...defaults, ...overrides }; } type MergeFn = typeof merge; // (overrides: Partial<Options>) => Options type Result = ReturnType<typeof merge>; // Options

typeof only works on identifiers and property paths (typeof obj.prop), not on arbitrary expressions such as typeof fn(). Combined with as const, it turns a runtime constant into an exact type, which is how most projects define enums-like lookups without the enum keyword.

Indexed Access Types

T[K] looks up the type of property K on T. The key can be a literal, a union of literals, keyof T, or number for arrays:

typescript
interface Order { id: string; customer: { name: string; vip: boolean }; lines: { sku: string; qty: number }[]; } type Customer = Order["customer"]; // { name: string; vip: boolean } type Line = Order["lines"][number]; // { sku: string; qty: number } type IdOrCustomer = Order["id" | "customer"]; // string | { name: string; vip: boolean } type AnyField = Order[keyof Order]; // union of all property types type Sku = Order["lines"][number]["sku"]; // string

Indexed access is purely a type operation. Order["id"] is a type, so it cannot appear where a value is expected, and the index must be a type as well (Order[typeof key] if key is a runtime constant, not Order[key]).

Combining the Three

The operators are designed to chain. Two patterns appear in almost every project.

Union from a runtime array

typescript
export const ROLES = ["admin", "editor", "viewer"] as const; export type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer" function isRole(x: string): x is Role { return (ROLES as readonly string[]).includes(x); }

The runtime array validates input; the derived type checks the rest of the code. Changing the list updates both.

Type-safe property access

typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const order: Order = { id: "o1", customer: { name: "Ada", vip: true }, lines: [] }; getProperty(order, "customer").vip; // boolean getProperty(order, "total"); // Error: "total" is not a key of Order

The same idea powers typed event emitters, form field helpers and translation lookups keyed by keyof typeof messages.

Quick Reference

| Expression | Produces | |---|---| | keyof T | Union of property names of T | | typeof value (type position) | Static type of a variable, property or function | | T["prop"] | Type of that property | | T[keyof T] | Union of all property value types | | T[number] | Element type of an array or tuple | | (typeof arr)[number] | Union of a const array's literal values | | keyof typeof obj | Union of an object value's keys |

Common mistakes

  • Writing typeof on an expression (typeof getUser()); wrap the call in a variable or use ReturnType<typeof getUser>.
  • Forgetting as const, so typeof ROLES becomes string[] and [number] yields plain string.
  • Passing a string where keyof T is required; narrow it with a guard first, or type the parameter as keyof T from the start.
Quick Quiz
Question 1 of 3

What is `keyof { a: number; b?: string }`?

Key Takeaways

  • keyof T gives the union of property names; with an index signature it becomes string | number.
  • Type-level typeof captures the type of a variable or function; pair it with as const for exact literal types.
  • T[K] reads a property's type; T[number] reads an array's element type; T[keyof T] unions all values.
  • (typeof ARR)[number] derives a union from a runtime list, keeping one source of truth.
  • K extends keyof T with return type T[K] is the foundation of type-safe property access.

Next lesson: Utility Types: Partial, Pick, Omit, Record and More — the built-in helpers that transform existing types.

keyof, typeof and Indexed Access Types - TypeScript | CodeYourCraft | CodeYourCraft