Keyof Operator in TypeScript
The keyof operator extracts the keys of an object type as a union of string literals. Learn how to use it for type-safe property access, generic constraints, and mapped types.
The TypeScript keyof operator takes an object type and produces a union of its keys as string literal types. It turns property names into types you can use in other type expressions.
Here is the simplest example:
type User = {
id: number;
name: string;
email: string;
};
type UserKeys = keyof User;The resulting type is "id" | "name" | "email". TypeScript looks at every property name and creates a union of those exact strings.
You can use the operator to type a variable that holds a property name:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: "Ada", email: "ada@example.com" };
const name = getProperty(user, "name");
const id = getProperty(user, "id");The constraint means the key argument must be one of the property names of the object type. TypeScript autocompletes valid keys at the call site. Calling with "name" returns a string, and calling with "id" returns a number.
If you try a key that does not exist, TypeScript catches it immediately:
getProperty(user, "age");The key "age" is not a key of User, so TypeScript rejects the call before it ever runs, with this exact error:
Argument of type '"age"' is not assignable to parameter of type 'keyof User'.Keyof with Different Object Shapes
The operator behaves differently depending on the object type. For a plain object type, it produces a union of the literal property names:
type Point = { x: number; y: number };
type P = keyof Point;The resulting type is "x" | "y". Both keys become string literal types in the union.
For an object with an index signature:
type StringMap = { [key: string]: boolean };
type SM = keyof StringMap;The result is string | number. JavaScript always coerces object keys to strings, and numeric indices are valid for string-indexed objects. TypeScript includes number in the result for string index signatures to reflect this runtime behavior.
type NumberIndexed = { [index: number]: string };
type NI = keyof NumberIndexed;The result is just number. When only a number index signature exists, the operator returns the number type.
Keyof on a Union of Object Types
When you apply the operator to a union of object types, you get only the keys that exist in every member of the union:
type A = { x: number; y: number };
type B = { y: number; z: number };
type CommonKeys = keyof (A | B);The result is "y". The key y exists on both types, but x only exists on A and z only exists on B. TypeScript computes the intersection of the key sets so only guaranteed-safe keys are included.
This is different from taking the union of the two keyof results, which would give all three keys:
type AllKeys = keyof A | keyof B;This result includes every key from either type. But you cannot safely use x on a value of type A | B because B does not have x, which is why applying the operator to the union only gives you "y".
Keyof with Indexed Access Types
The operator pairs naturally with indexed access types to get both the key and its corresponding value type. Reusing the User type from earlier:
type UserProperty<T extends keyof User> = {
key: T;
value: User[T];
};
const prop: UserProperty<"name"> = {
key: "name",
value: "Ada",
};The expression User[T] is an indexed access type. When T is "name", User["name"] evaluates to string. This pattern is useful for building type-safe configuration objects and form field definitions.
A Practical Pattern: Type-Safe Object Mapping
You can use the operator to write a function that maps over an object's values while preserving the keys:
function mapValues<T, V>(
obj: T,
fn: (value: T[keyof T]) => V
): { [K in keyof T]: V } {
const result = {} as { [K in keyof T]: V };
for (const key in obj) {
result[key] = fn(obj[key]);
}
return result;
}The mapped type { [K in keyof T]: V } creates a new object type with the same keys as the input but with all values replaced by the output type of the mapping function. Calling the function on a real object shows this in practice:
const scores = { math: 90, english: 85, science: 92 };
const grades = mapValues(scores, (s) => (s >= 90 ? "A" : "B"));
console.log(grades.math);
console.log(grades.english);The result grades keeps the same three keys, but each value is now the letter-grade string returned by the mapping function, with full autocomplete.
Mapped types are covered in more detail in mapped types in TypeScript.
Keyof Is Compile-Time Only
The operator is compile-time only. It does not exist at runtime and does not appear in the emitted JavaScript:
type Keys = keyof { a: 1; b: 2 };The type is only used by the compiler. It is not a value you can log or iterate over. If you need the keys at runtime, use Object.keys() instead:
const obj = { a: 1, b: 2 };
const runtimeKeys = Object.keys(obj);The variable runtimeKeys has type string[], not a tuple of the specific keys, because Object.keys always returns string[] for type safety: the object might have more keys at runtime than the type declares.
Common Mistakes
Confusing keyof with Object.keys:
const user = { name: "Ada", age: 30 };
const keys = Object.keys(user);The variable has type string[], not a union of the specific keys, for the same reason covered above: the runtime function cannot know the object will never grow extra properties.
Using the operator on a value instead of a type:
const obj = { x: 10, y: 20 };
type Bad = keyof obj;This fails because obj is a value, not a type. Use the typeof type operator to get the type first:
type Good = keyof typeof obj;The typeof operator gets the type of the value, and then keyof extracts its keys. For more on this, see typeof type operator in TypeScript.
Expecting the operator to return all keys from a union:
type A = { x: number };
type B = { y: number };
type All = keyof (A | B);The result is never because no key exists on both A and B. If you want all keys from either type, take the union of the two keyof results instead.
For more on how the operator combines with generics, see generic constraints in TypeScript.
Rune AI
Key Insights
keyof Tproduces a union of the string literal keys of object type T.- Use
K extends keyof Tto constrain a type parameter to valid property names of T. - Combine keyof with indexed access types (
T[K]) to get the value type of a specific key. - keyof is a compile-time type operator. It does not exist in the emitted JavaScript.
- keyof on a union of object types returns only the keys common to all members.
Frequently Asked Questions
What does keyof return for an object with an index signature?
Can I use keyof on a union type?
What is the difference between keyof and Object.keys?
Conclusion
The keyof operator turns an object type's keys into a union of string literal types. It is most useful when combined with generic constraints: <K extends keyof T> guarantees that a key actually exists on an object. From there you can build type-safe property accessors, object mappers, and generic utilities that the compiler can verify. keyof is a compile-time only operator. It produces types, not runtime values.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.