ProgrammingFullstack Developer

How does the keyof operator work in TypeScript, what is it used for, and how does it influence the design of safe functions and APIs?

Pass interviews with Hintsage AI assistant

Answer.

History of the issue

TypeScript has been developed from the very beginning for strict typing of objects and writing more predictable code in JavaScript. One of the tools for ensuring safety when accessing object properties became the keyof operator, which appeared in TypeScript 2.1. It allows creating types that represent a set of string (and symbol) keys available in the declared object type.

The problem

In pure JavaScript, object properties can be accessed by any string, and if there is a mistake - it will result in undefined or an error that will be found only at runtime. Without strict typing, it's easy to make a typo or forget to update a key string during refactoring. Additionally, there are often needs to build a function that only accepts valid keys of a specific object or type.

The solution

The keyof operator creates a union type from all the keys of a type. With its help, one can limit valid keys, increase API safety (e.g., for getter/setter functions), and create generic utility types.

Example code:

type User = { name: string; age: number; active: boolean }; type UserKey = keyof User; // "name" | "age" | "active" function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user: User = { name: 'Tom', age: 33, active: true }; const age = getProp(user, 'age'); // age type is number

Key features:

  • Allows declaring "key" types that depend on the object's structure.
  • Makes get/set property functions as type-safe as possible - typos and non-existent keys are excluded.
  • Works with symbol properties starting from TypeScript 2.7 and above.

Tricky questions.

What does keyof return for an array, and can you get indexes?

keyof for an array returns the types of keys of an object, which the array actually is in JS. Typically, this includes index strings and special array properties.

Example code:

type A = keyof number[]; // "length" | "toString" | "pop" | ... | number

Can keyof return keys of union-types?

Yes, keyof returns the intersection of the keys of both objects from the union type, not their union.

Example code:

type A = {a: string, b: number } type B = {b: number, c: boolean } type C = keyof (A | B); // "b"

What happens if an object's property is optional? Does keyof support "?"?

Yes, optional properties are also included in the resulting type of keys, but that does not mean the property is necessarily present on the object at runtime.

Example code:

type D = { a: string; b?: number }; type DK = keyof D; // "a" | "b"

Type errors and anti-patterns

  • Using constant strings for keys instead of keyof - a loss of typing.
  • Assuming that keyof for a union returns a union rather than an intersection.
  • Expecting that keyof considers only explicitly declared keys and not inherited ones.

Real-life example

Negative case

A function getProperty(obj, key) is written with a string type key and called with a non-existent key - an error appears only at runtime.

Pros:

  • Universal code.

Cons:

  • No compile-time checking, possible errors during refactoring, vulnerability to typos.

Positive case

Generics are used: getProperty<T, K extends keyof T>(), the key type is strictly constrained to the keys of the structure.

Pros:

  • Absolute type safety; errors from typos or mistakes in keys are impossible.

Cons:

  • The code is slightly more complex; generics may not always be clear to beginners.