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.

6 min read

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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

texttext
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:

typescripttypescript
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:

typescripttypescript
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.

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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

Rune AI

Key Insights

  • keyof T produces a union of the string literal keys of object type T.
  • Use K extends keyof T to 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.
RunePowered by Rune AI

Frequently Asked Questions

What does keyof return for an object with an index signature?

For a string index signature like `{ [k: string]: unknown }`, keyof returns `string | number`. For a number index signature like `{ [n: number]: unknown }`, keyof returns `number`. The `number` also shows up with string index signatures because JavaScript coerces numeric keys to strings at runtime.

Can I use keyof on a union type?

Yes. `keyof (A | B)` returns the keys that exist on both A and B: the intersection of their key sets. This is because only the shared keys are guaranteed to exist on every member of the union.

What is the difference between keyof and Object.keys?

keyof is a type operator that runs at compile time and produces a type. Object.keys is a runtime function that returns an array of strings. keyof gives you the exact literal keys. Object.keys returns `string[]`.

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.