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 keyBasic 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'); // ErrorGeneric 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 }); // ErrorBest 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.