Mastering `typeof` and `keyof` in TypeScript: Every Real-World Use You Need to Know
15 Real-World Examples for Deeply Nested Types and Generics
In this article, I’ll break down what `typeof` and `keyof` operators do, why they’re indispensable, and how you can use them like a pro. No fluff. Just the straight talk you need.
What are typeof
and keyofs are ?
typeof
is used to get the type of a variable or expression. It allows you to capture the type information of a value to reuse it elsewhere in your code.keyof
is a type operator that extracts the keys of an object type as a union of string literal types. It’s useful for creating types based on the property names of an object.
Code Examples:
1. Extract type from a variable with typeof
const settings = { darkMode: true, version: 2 };
// Extract the type of 'settings' automatically
type SettingsType = typeof settings;
// SettingsType is:
// {
// darkMode: boolean;
// version: number;
// }
2. Get keys of a type with keyof
const settings = { darkMode: true, version: 2 };
type SettingsKeys = keyof SettingsType;
// "darkMode" | "version"
3. Use keyof
to restrict function keys
const settings = { darkMode: true, version: 2 };
type SettingsKeys = keyof SettingsType;
// "darkMode" | "version"
function getSetting(key: SettingsKeys) {
// Function can only accept "darkMode" or "version"
}
// Valid call:
getSetting("darkMode");
// Invalid call (TypeScript error):
// getSetting("invalidKey");
4. Get keys of a nested object with interfaces
interface Profile {
name: string;
age: number;
}
interface User {
profile: Profile;
active: boolean;
}
const user: User = {
profile: { name: "Bob", age: 30 },
active: true,
};
// Extract keys of nested object
type ProfileKeys = keyof User["profile"];
// "name" | "age"
5. Generic function to get property value safely
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Alice", age: 25 };
// Valid usage:
const name = getProperty(person, "name"); // type: string, value: "Alice"
// Invalid usage (TypeScript error):
// const invalid = getProperty(person, "invalidKey");
6. Use typeof
with arrays and extract element type
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
// Extract the type of an element in the array
type User = typeof users[number];
// User is:
// {
// id: number;
// name: string;
// }
7. Get keys of array element type
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
type UserKeys = keyof typeof users[number];
// "id" | "name"
8. Nested generic property getter
function getNestedProp<
T,
K1 extends keyof T,
K2 extends keyof T[K1]
>(obj: T, key1: K1, key2: K2): T[K1][K2] {
return obj[key1][key2];
}
const company = {
address: {
city: "Metropolis",
zip: 12345,
}
};
const city = getNestedProp(company, "address", "city");
// city: string = "Metropolis"
9. Extract keys from deeply nested object
const company = {
address: {
geo: { lat: 40.7, lng: -74 },
}
};
type GeoKeys = keyof typeof company.address.geo;
// "lat" | "lng"
10. Generic helper to extract array element type
type ElementType<T> = T extends (infer U)[] ? U : never;
const employees = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
type Employee = ElementType<typeof employees>;
// Employee is:
// {
// id: number;
// name: string;
// }
11. Combine typeof
and keyof
to create a union of keys
const config = { host: "localhost", port: 8080 };
type ConfigKeys = keyof typeof config;
// "host" | "port"
12. Use keyof
in mapped types for transformations
type ReadonlyConfig = {
readonly [K in keyof typeof config]: typeof config[K];
};
const readonlyConfig: ReadonlyConfig = {
host: "localhost",
port: 8080,
};
// readonlyConfig.host = "127.0.0.1"; // Error: Cannot assign to 'host' because it is a read-only property
13. Get keys from union types
type Roles = "admin" | "user";
type RoleKeys = keyof { admin: string; user: string };
// "admin" | "user"
14. Recursive deep key union (advanced)
type DeepKeys<T> = T extends object
? { [K in keyof T]: K | `${K & string}.${DeepKeys<T[K]> & string}` }[keyof T]
: never;
interface Company {
name: string;
address: {
street: string;
geo: {
lat: number;
lng: number;
}
};
employees: {
id: number;
name: string;
}[];
}
type CompanyDeepKeys = DeepKeys<Company>;
/* CompanyDeepKeys resolves to union of:
"name"
| "address"
| "address.street"
| "address.geo"
| "address.geo.lat"
| "address.geo.lng"
| "employees"
| "employees.id"
| "employees.name"
*/
15. Generic deep property getter with tuple keys
function getDeepProp<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(
obj: T,
key1: K1,
key2: K2,
key3: K3
): T[K1][K2][K3] {
return obj[key1][key2][key3];
};
const company: Company = {
name: "TechCorp",
address: {
street: "123 Main St",
geo: { lat: 40.7, lng: -74 }
},
employees: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]
};
const latitude = getDeepProp(company, "address", "geo", "lat");
// latitude: number = 40.7
Explanation
typeof
extracts the type of a value or variable so you avoid duplicating type declarations.keyof
extracts all keys from a type as a union of string literals, allowing you to limit keys in generics or function parameters.Combining
typeof
andkeyof
lets you dynamically build types and keys based on actual data shapes.Indexed access types (
T[K]
) let you drill down into nested objects safely.Generics with
keyof
constrain keys at compile time, preventing invalid property access.Advanced patterns like recursive key unions let you generate deep nested property paths.
Helper generics (like extracting array element types) make your utilities reusable for any data shape.
🔗 Resources:
❤️ If you like my work, please follow me and subscribe ❤️