ESC

Type to search the knowledge base.

keyof typeof and Indexed Access

Derive keys and value types from objects with keyof, typeof, and T[K] — the toolkit behind typed forms, configs, and safe property access.

intermediate3 min read
  • typescript
  • keyof-typeof

Three operators show up constantly in real TypeScript:

  • typeof (type query) — type from a value
  • keyof — union of keys of a type
  • Indexed access T[K] — type of property K on T

Together they keep configs, forms, and dictionaries typed without duplicating lists of keys.

Docs: typeof, keyof, Indexed Access.

typeof in type position

const theme = {
  bg: '#0b0b0b',
  fg: '#f5f5f5',
  accent: '#6ee7b7',
} as const;

type Theme = typeof theme;
// { readonly bg: '#0b0b0b'; readonly fg: '#f5f5f5'; readonly accent: '#6ee7b7' }

Value space typeof (JS) and type space typeof (TS) share a keyword; context decides.

keyof

type ThemeKey = keyof Theme; // 'bg' | 'fg' | 'accent'

function getToken(key: ThemeKey) {
  return theme[key];
}

Without keyof, people type key: string and lose safety.

Indexed access

type Accent = Theme['accent']; // '#6ee7b7'
type AnyColor = Theme[keyof Theme]; // union of color literals
type User = { id: string; age: number; active: boolean };
type UserId = User['id']; // string
type UserField = User[keyof User]; // string | number | boolean

The golden helper: getProp

function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const age = getProp({ id: '1', age: 42 }, 'age'); // number

Config maps and routes

const routes = {
  home: '/',
  user: '/users/:id',
  settings: '/settings',
} as const;

type RouteName = keyof typeof routes;
type RoutePath = (typeof routes)[RouteName];

function href(name: RouteName): string {
  return routes[name];
}

Forms: field errors typed to fields

type FormValues = {
  email: string;
  password: string;
};

type FormErrors = Partial<Record<keyof FormValues, string>>;

const errors: FormErrors = {
  email: 'Required',
  // username: '…' // error — not a key
};

See Record and Partial patterns for forms.

keyof and index signatures

type Dict = { [key: string]: number };
type K = keyof Dict; // string | number  (historical number key coercion)

For stricter keys, prefer unions:

type Strict = Record<'a' | 'b', number>;
type SK = keyof Strict; // 'a' | 'b'

typeof on functions and classes

function createUser(name: string) {
  return { id: crypto.randomUUID(), name };
}

type CreateUser = typeof createUser;
type User = ReturnType<CreateUser>;
class Store {
  value = 0;
}
type StoreInstance = InstanceType<typeof Store>;

Mapped types connection

type OptionalFlags<T> = {
  [K in keyof T]?: boolean;
};

type UserFlags = OptionalFlags<User>;
// { id?: boolean; age?: boolean; active?: boolean }

See mapped types intro.

Footguns

Footgun Detail
Forgetting as const Keys widen; literals become string
keyof on arrays Includes number + array methods if not careful
Using JS typeof null Returns 'object' — different problem
obj[key] with key: string Needs index signature or cast
const keys = Object.keys(theme); // string[] — not (keyof Theme)[]
// assert or validate when mapping
(Object.keys(theme) as Array<keyof typeof theme>).forEach((k) => {
  console.log(theme[k]);
});

Interview out-loud answer

“typeof pulls a type from a value, keyof builds a key union, and T[K] reads a property type. Together they type safe accessors and configs derived from a single source of truth. K extends keyof T is the standard constraint.”

Further reading

Related guides