Back to Blog
TypeScriptkeyoftypeofType Operators

TypeScript keyof and typeof Operators Guide

Master TypeScript keyof and typeof operators for creating dynamic types from values and keys.

B
Bootspring Team
Engineering
September 12, 2019
7 min read

The keyof and typeof operators enable powerful type manipulation by extracting types from values and keys. Here's how to use them.

Basic keyof#

1// keyof extracts keys as union type 2interface User { 3 id: number; 4 name: string; 5 email: string; 6} 7 8type UserKeys = keyof User; 9// type UserKeys = "id" | "name" | "email" 10 11// Use with generics 12function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { 13 return obj[key]; 14} 15 16const user: User = { id: 1, name: 'John', email: 'john@example.com' }; 17 18const name = getProperty(user, 'name'); // type: string 19const id = getProperty(user, 'id'); // type: number 20// getProperty(user, 'invalid'); // Error: invalid key

Basic typeof#

1// typeof extracts type from value 2const config = { 3 apiUrl: 'https://api.example.com', 4 timeout: 5000, 5 retries: 3, 6}; 7 8type Config = typeof config; 9// type Config = { 10// apiUrl: string; 11// timeout: number; 12// retries: number; 13// } 14 15// Use with functions 16function createUser(name: string, age: number) { 17 return { name, age, createdAt: new Date() }; 18} 19 20type User = ReturnType<typeof createUser>; 21// type User = { 22// name: string; 23// age: number; 24// createdAt: Date; 25// }

Combining keyof and typeof#

1// Get keys from an object value 2const routes = { 3 home: '/', 4 about: '/about', 5 contact: '/contact', 6 blog: '/blog', 7}; 8 9type RouteNames = keyof typeof routes; 10// type RouteNames = "home" | "about" | "contact" | "blog" 11 12function navigate(route: RouteNames) { 13 window.location.href = routes[route]; 14} 15 16navigate('home'); // OK 17navigate('about'); // OK 18// navigate('invalid'); // Error 19 20// With arrays using const assertion 21const colors = ['red', 'green', 'blue'] as const; 22 23type Color = typeof colors[number]; 24// type Color = "red" | "green" | "blue"

Object Property Access Types#

1// Access nested types 2interface Response { 3 data: { 4 users: Array<{ 5 id: number; 6 profile: { 7 name: string; 8 avatar: string; 9 }; 10 }>; 11 }; 12 meta: { 13 page: number; 14 total: number; 15 }; 16} 17 18type Users = Response['data']['users']; 19type User = Response['data']['users'][number]; 20type Profile = Response['data']['users'][number]['profile']; 21type Meta = Response['meta']; 22 23// Dynamic key access 24type DataKeys = keyof Response['data']; 25// type DataKeys = "users"

Enum-like Objects#

1// Object enum pattern 2const Status = { 3 Pending: 'pending', 4 Active: 'active', 5 Completed: 'completed', 6 Cancelled: 'cancelled', 7} as const; 8 9type StatusType = typeof Status; 10// { 11// readonly Pending: "pending"; 12// readonly Active: "active"; 13// readonly Completed: "completed"; 14// readonly Cancelled: "cancelled"; 15// } 16 17type StatusKeys = keyof typeof Status; 18// "Pending" | "Active" | "Completed" | "Cancelled" 19 20type StatusValues = typeof Status[keyof typeof Status]; 21// "pending" | "active" | "completed" | "cancelled" 22 23function setStatus(status: StatusValues) { 24 console.log(status); 25} 26 27setStatus(Status.Active); // OK 28setStatus('pending'); // OK 29// setStatus('invalid'); // Error

Generic Constraints#

1// Constrain generic with keyof 2function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { 3 const result = {} as Pick<T, K>; 4 keys.forEach((key) => { 5 result[key] = obj[key]; 6 }); 7 return result; 8} 9 10const user = { 11 id: 1, 12 name: 'John', 13 email: 'john@example.com', 14 age: 30, 15}; 16 17const subset = pick(user, ['name', 'email']); 18// type: { name: string; email: string } 19 20// Exclude keys 21function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> { 22 const result = { ...obj }; 23 keys.forEach((key) => delete result[key]); 24 return result; 25} 26 27const withoutEmail = omit(user, ['email']); 28// type: { id: number; name: string; age: number }

Mapped Types with keyof#

1// Make all properties optional 2type Optional<T> = { 3 [K in keyof T]?: T[K]; 4}; 5 6// Make all properties readonly 7type Readonly<T> = { 8 readonly [K in keyof T]: T[K]; 9}; 10 11// Make all properties nullable 12type Nullable<T> = { 13 [K in keyof T]: T[K] | null; 14}; 15 16// Prefix keys 17type Prefixed<T, P extends string> = { 18 [K in keyof T as `${P}${string & K}`]: T[K]; 19}; 20 21interface User { 22 name: string; 23 age: number; 24} 25 26type PrefixedUser = Prefixed<User, 'user_'>; 27// { user_name: string; user_age: number }

Type Guards with keyof#

1// Check if key exists in object 2function hasKey<T extends object>( 3 obj: T, 4 key: PropertyKey 5): key is keyof T { 6 return key in obj; 7} 8 9const data = { name: 'John', age: 30 }; 10 11if (hasKey(data, 'name')) { 12 console.log(data.name); // TypeScript knows 'name' exists 13} 14 15// Safe property access 16function safeGet<T extends object, K extends PropertyKey>( 17 obj: T, 18 key: K 19): K extends keyof T ? T[K] : undefined { 20 return (obj as any)[key]; 21}

Function Parameter Types#

1// Extract function parameter types 2function updateUser(id: number, data: { name?: string; email?: string }) { 3 // ... 4} 5 6type UpdateUserParams = Parameters<typeof updateUser>; 7// type UpdateUserParams = [id: number, data: { name?: string; email?: string }] 8 9type UpdateData = Parameters<typeof updateUser>[1]; 10// type UpdateData = { name?: string; email?: string } 11 12// Extract return type 13type UpdateResult = ReturnType<typeof updateUser>;

Conditional Types with keyof#

1// Filter keys by value type 2type KeysOfType<T, V> = { 3 [K in keyof T]: T[K] extends V ? K : never; 4}[keyof T]; 5 6interface User { 7 id: number; 8 name: string; 9 email: string; 10 age: number; 11 isActive: boolean; 12} 13 14type StringKeys = KeysOfType<User, string>; 15// type StringKeys = "name" | "email" 16 17type NumberKeys = KeysOfType<User, number>; 18// type NumberKeys = "id" | "age" 19 20// Pick only string properties 21type StringProperties<T> = Pick<T, KeysOfType<T, string>>; 22 23type UserStrings = StringProperties<User>; 24// { name: string; email: string }

Index Signatures#

1// keyof with index signatures 2interface Dictionary { 3 [key: string]: string; 4} 5 6type DictKeys = keyof Dictionary; 7// type DictKeys = string | number (number because arr[0] === arr["0"]) 8 9// Restrict to specific keys plus index 10interface Config { 11 name: string; 12 version: number; 13 [key: string]: string | number; 14} 15 16type ConfigKeys = keyof Config; 17// type ConfigKeys = string | number 18 19// Better approach with explicit keys 20type StrictConfig = { 21 name: string; 22 version: number; 23} & Record<string, string | number>;

Class Types#

1// keyof with classes 2class User { 3 constructor( 4 public id: number, 5 public name: string, 6 private password: string 7 ) {} 8 9 updateName(name: string) { 10 this.name = name; 11 } 12} 13 14type UserKeys = keyof User; 15// type UserKeys = "id" | "name" | "updateName" 16// Note: private members excluded 17 18// typeof with class 19type UserType = typeof User; 20// Constructor type 21 22type UserInstance = InstanceType<typeof User>; 23// Instance type (same as User)

Template Literal Types#

1// Combine keyof with template literals 2interface API { 3 getUser: () => User; 4 getUsers: () => User[]; 5 createUser: (data: UserData) => User; 6 deleteUser: (id: number) => void; 7} 8 9type Getters = Extract<keyof API, `get${string}`>; 10// type Getters = "getUser" | "getUsers" 11 12type APIMethod = `${keyof API}Async`; 13// type APIMethod = "getUserAsync" | "getUsersAsync" | "createUserAsync" | "deleteUserAsync" 14 15// Generate event handlers 16interface Events { 17 click: MouseEvent; 18 scroll: Event; 19 keydown: KeyboardEvent; 20} 21 22type EventHandlers = { 23 [K in keyof Events as `on${Capitalize<K>}`]: (event: Events[K]) => void; 24}; 25// { 26// onClick: (event: MouseEvent) => void; 27// onScroll: (event: Event) => void; 28// onKeydown: (event: KeyboardEvent) => void; 29// }

Real-World Examples#

1// Type-safe event emitter 2class TypedEmitter<Events extends Record<string, any>> { 3 private listeners: Partial<Record<keyof Events, Function[]>> = {}; 4 5 on<K extends keyof Events>( 6 event: K, 7 listener: (data: Events[K]) => void 8 ): void { 9 if (!this.listeners[event]) { 10 this.listeners[event] = []; 11 } 12 this.listeners[event]!.push(listener); 13 } 14 15 emit<K extends keyof Events>(event: K, data: Events[K]): void { 16 this.listeners[event]?.forEach((fn) => fn(data)); 17 } 18} 19 20// Usage 21interface AppEvents { 22 login: { userId: string }; 23 logout: undefined; 24 error: Error; 25} 26 27const emitter = new TypedEmitter<AppEvents>(); 28 29emitter.on('login', (data) => { 30 console.log(data.userId); // type-safe 31}); 32 33emitter.emit('login', { userId: '123' }); // OK 34// emitter.emit('login', { invalid: true }); // Error

Best Practices#

keyof Usage: ✓ Type-safe property access ✓ Generic constraints ✓ Mapped types ✓ Dynamic key validation typeof Usage: ✓ Extract types from values ✓ Configuration objects ✓ Function return types ✓ Const assertions Combinations: ✓ keyof typeof for objects ✓ ReturnType<typeof fn> ✓ Parameters<typeof fn> ✓ InstanceType<typeof Class> Avoid: ✗ Overcomplicating simple types ✗ Ignoring index signatures ✗ Missing const assertions ✗ Excessive type inference

Conclusion#

The keyof and typeof operators are fundamental for TypeScript type manipulation. Use keyof to extract union types of object keys, typeof to derive types from values, and combine them for powerful patterns like enum-like objects and type-safe property access. These operators enable writing generic, reusable code while maintaining full type safety.

Share this article

Help spread the word about Bootspring