Back to Blog
TypeScriptkeyoftypeofTypes

TypeScript keyof and typeof Guide

Master TypeScript keyof and typeof operators for powerful type transformations.

B
Bootspring Team
Engineering
August 28, 2018
8 min read

The keyof and typeof operators are fundamental tools for building dynamic, type-safe code in TypeScript.

typeof Operator#

1// typeof extracts type from a value 2const user = { 3 name: 'John', 4 age: 30, 5 active: true 6}; 7 8type User = typeof user; 9// { name: string; age: number; active: boolean } 10 11// Works with any value 12const PI = 3.14159; 13type PIType = typeof PI; // number 14 15// Const assertion gives literal types 16const config = { 17 host: 'localhost', 18 port: 3000 19} as const; 20 21type Config = typeof config; 22// { readonly host: 'localhost'; readonly port: 3000 } 23 24// Function types 25function greet(name: string): string { 26 return `Hello, ${name}`; 27} 28 29type GreetFn = typeof greet; 30// (name: string) => string

keyof Operator#

1// keyof extracts keys as a union type 2interface User { 3 id: number; 4 name: string; 5 email: string; 6} 7 8type UserKeys = keyof User; 9// 'id' | 'name' | 'email' 10 11// Works with any object type 12type ObjectKeys = keyof { a: 1; b: 2; c: 3 }; 13// 'a' | 'b' | 'c' 14 15// Array/tuple 16type ArrayKeys = keyof string[]; 17// number | 'length' | 'push' | 'pop' | ... (array methods) 18 19type TupleKeys = keyof [string, number]; 20// number | '0' | '1' | 'length' | ... 21 22// Index signatures 23type StringMap = { [key: string]: unknown }; 24type StringMapKeys = keyof StringMap; 25// string | number (number keys coerce to string) 26 27type NumberMap = { [key: number]: unknown }; 28type NumberMapKeys = keyof NumberMap; 29// number

Combining keyof and typeof#

1// Get keys from a value's type 2const themes = { 3 light: { bg: '#fff', text: '#000' }, 4 dark: { bg: '#000', text: '#fff' }, 5 blue: { bg: '#00f', text: '#fff' } 6}; 7 8type ThemeNames = keyof typeof themes; 9// 'light' | 'dark' | 'blue' 10 11// Type-safe theme selector 12function getTheme(name: keyof typeof themes) { 13 return themes[name]; 14} 15 16getTheme('light'); // OK 17// getTheme('green'); // Error 18 19// Enum-like object 20const Status = { 21 Pending: 0, 22 Active: 1, 23 Completed: 2 24} as const; 25 26type StatusKeys = keyof typeof Status; 27// 'Pending' | 'Active' | 'Completed' 28 29type StatusValues = typeof Status[StatusKeys]; 30// 0 | 1 | 2

Generic Constraints#

1// Constrain generic to object keys 2function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { 3 return obj[key]; 4} 5 6const user = { name: 'John', age: 30 }; 7 8getProperty(user, 'name'); // string 9getProperty(user, 'age'); // number 10// getProperty(user, 'email'); // Error: 'email' not in keyof user 11 12// Multiple key access 13function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { 14 const result = {} as Pick<T, K>; 15 keys.forEach(key => { 16 result[key] = obj[key]; 17 }); 18 return result; 19} 20 21const picked = pick(user, ['name']); 22// { name: string }

Indexed Access Types#

1interface User { 2 id: number; 3 name: string; 4 address: { 5 street: string; 6 city: string; 7 }; 8 tags: string[]; 9} 10 11// Access property type 12type UserId = User['id']; // number 13type UserName = User['name']; // string 14 15// Nested access 16type City = User['address']['city']; // string 17 18// Array element type 19type Tag = User['tags'][number]; // string 20 21// Union of property types 22type IdOrName = User['id' | 'name']; // number | string 23 24// All property types 25type AllValues = User[keyof User]; 26// number | string | { street: string; city: string } | string[]

Mapped Types with keyof#

1// Make all properties optional 2type Partial<T> = { 3 [K in keyof T]?: T[K]; 4}; 5 6// Make all properties required 7type Required<T> = { 8 [K in keyof T]-?: T[K]; 9}; 10 11// Make all properties readonly 12type Readonly<T> = { 13 readonly [K in keyof T]: T[K]; 14}; 15 16// Pick specific properties 17type Pick<T, K extends keyof T> = { 18 [P in K]: T[P]; 19}; 20 21// Custom mapped type 22type Nullable<T> = { 23 [K in keyof T]: T[K] | null; 24}; 25 26interface User { 27 id: number; 28 name: string; 29} 30 31type NullableUser = Nullable<User>; 32// { id: number | null; name: string | null }

Key Remapping#

1// Rename keys with 'as' 2type Getters<T> = { 3 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; 4}; 5 6interface User { 7 name: string; 8 age: number; 9} 10 11type UserGetters = Getters<User>; 12// { getName: () => string; getAge: () => number } 13 14// Filter keys 15type OnlyStrings<T> = { 16 [K in keyof T as T[K] extends string ? K : never]: T[K]; 17}; 18 19interface Mixed { 20 id: number; 21 name: string; 22 email: string; 23 active: boolean; 24} 25 26type StringProps = OnlyStrings<Mixed>; 27// { name: string; email: string } 28 29// Exclude specific keys 30type OmitId<T> = { 31 [K in keyof T as K extends 'id' ? never : K]: T[K]; 32};

Template Literal Types with keyof#

1// Event handlers 2type EventHandlers<T> = { 3 [K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void; 4}; 5 6interface Form { 7 name: string; 8 email: string; 9 age: number; 10} 11 12type FormHandlers = EventHandlers<Form>; 13// { 14// onNameChange: (value: string) => void; 15// onEmailChange: (value: string) => void; 16// onAgeChange: (value: number) => void; 17// } 18 19// API endpoints 20type ApiEndpoints<T> = { 21 [K in keyof T as `fetch${Capitalize<string & K>}`]: () => Promise<T[K]>; 22} & { 23 [K in keyof T as `update${Capitalize<string & K>}`]: (value: T[K]) => Promise<void>; 24}; 25 26interface Data { 27 user: User; 28 posts: Post[]; 29} 30 31type DataApi = ApiEndpoints<Data>; 32// { 33// fetchUser: () => Promise<User>; 34// fetchPosts: () => Promise<Post[]>; 35// updateUser: (value: User) => Promise<void>; 36// updatePosts: (value: Post[]) => Promise<void>; 37// }

ReturnType and Parameters#

1// Get function return type 2function createUser(name: string, age: number) { 3 return { id: Date.now(), name, age }; 4} 5 6type UserReturn = ReturnType<typeof createUser>; 7// { id: number; name: string; age: number } 8 9// Get parameter types 10type UserParams = Parameters<typeof createUser>; 11// [name: string, age: number] 12 13type FirstParam = Parameters<typeof createUser>[0]; // string 14type SecondParam = Parameters<typeof createUser>[1]; // number 15 16// Constructor parameters 17class User { 18 constructor(public name: string, public age: number) {} 19} 20 21type UserConstructorParams = ConstructorParameters<typeof User>; 22// [name: string, age: number]

Real-World Patterns#

1// Type-safe object entries 2function entries<T extends object>(obj: T): [keyof T, T[keyof T]][] { 3 return Object.entries(obj) as [keyof T, T[keyof T]][]; 4} 5 6// Type-safe object keys 7function keys<T extends object>(obj: T): (keyof T)[] { 8 return Object.keys(obj) as (keyof T)[]; 9} 10 11// Type-safe property setter 12function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void { 13 obj[key] = value; 14} 15 16// Deep readonly 17type DeepReadonly<T> = { 18 readonly [K in keyof T]: T[K] extends object 19 ? DeepReadonly<T[K]> 20 : T[K]; 21}; 22 23// Path type for nested access 24type Path<T, K extends keyof T = keyof T> = K extends string 25 ? T[K] extends object 26 ? `${K}.${Path<T[K]>}` | K 27 : K 28 : never; 29 30interface User { 31 name: string; 32 address: { 33 street: string; 34 city: string; 35 }; 36} 37 38type UserPaths = Path<User>; 39// 'name' | 'address' | 'address.street' | 'address.city'

Discriminated Unions with keyof#

1// Type guard using keyof 2interface Circle { 3 kind: 'circle'; 4 radius: number; 5} 6 7interface Square { 8 kind: 'square'; 9 size: number; 10} 11 12type Shape = Circle | Square; 13 14function isCircle(shape: Shape): shape is Circle { 15 return shape.kind === 'circle'; 16} 17 18// Property existence check 19function hasKey<T extends object>(obj: T, key: keyof any): key is keyof T { 20 return key in obj; 21} 22 23const obj = { a: 1, b: 2 }; 24const key = 'a' as string; 25 26if (hasKey(obj, key)) { 27 console.log(obj[key]); // Type-safe access 28}

Best Practices#

1// DO: Use keyof for type-safe property access 2function getValue<T, K extends keyof T>(obj: T, key: K): T[K] { 3 return obj[key]; 4} 5 6// DO: Combine with typeof for value-derived types 7const config = { port: 3000, host: 'localhost' } as const; 8type ConfigKey = keyof typeof config; 9 10// DO: Use mapped types for transformations 11type Flags<T> = { [K in keyof T]: boolean }; 12 13// DON'T: Use keyof with 'any' 14type BadKeys = keyof any; // string | number | symbol (too broad) 15 16// DON'T: Forget 'as const' for literal types 17const routes = ['/', '/about', '/contact']; // string[] 18type Route = typeof routes[number]; // string (not literal union) 19 20const routes2 = ['/', '/about', '/contact'] as const; 21type Route2 = typeof routes2[number]; // '/' | '/about' | '/contact'

Summary#

typeof: ✓ Extract type from value ✓ Use with 'as const' for literals ✓ Get function types ✓ Derive types from implementation keyof: ✓ Get union of object keys ✓ Constrain generic parameters ✓ Build mapped types ✓ Type-safe property access Combinations: ✓ keyof typeof for value keys ✓ Indexed access T[K] ✓ Mapped types with key remapping ✓ Template literal transformations

Conclusion#

The keyof and typeof operators are powerful tools for building type-safe abstractions. Use typeof to derive types from runtime values and keyof to work with object keys as types. Combine them with mapped types, template literals, and indexed access to create sophisticated type transformations that catch errors at compile time while keeping your code DRY.

Share this article

Help spread the word about Bootspring