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) => stringkeyof 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// numberCombining 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 | 2Generic 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.