TypeScript keyof, typeof and Mapped Types

TypeScript type operators such as keyof and typeof let you build new types from existing types and values. They are the tools behind the utility types and make your types follow your code automatically.

keyof

keyof T produces a union of the property names of T.

type Settings = { theme: string; fontSize: number; autosave: boolean };
type SettingKey = keyof Settings; // "theme" | "fontSize" | "autosave"

function describe(key: SettingKey) {
  return "Setting: " + key;
}
console.log(describe("fontSize"));
// describe("color"); // Error
Setting: fontSize

typeof in Types

In a type position, typeof reads the type of an existing variable. Combined with as const, you can define data once and derive types from it.

const config = {
  apiUrl: "https://api.example.com",
  retries: 3,
  regions: ["eu", "us"],
} as const;

type Config = typeof config;
type Region = (typeof config.regions)[number]; // "eu" | "us"

const r: Region = "eu";
console.log(r, config.retries);
eu 3

Indexed Access Types

T["key"] looks up the type of a property, and [number] gives the element type of an array.

type Order = {
  id: number;
  customer: { name: string; city: string };
  items: { sku: string; qty: number }[];
};
type Customer = Order["customer"];      // { name: string; city: string }
type Item = Order["items"][number];     // { sku: string; qty: number }

const it: Item = { sku: "RICE-1", qty: 10 };
console.log(it.sku, it.qty);
RICE-1 10

Mapped Types

A mapped type loops over keys with in keyof and builds a new type. This is how Partial and Readonly are implemented.

type Flags<T> = { [K in keyof T]: boolean };
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };

type Person = { name: string; age: number };
const touched: Flags<Person> = { name: true, age: false };
const getters: Getters<Person> = { getName: () => "Hira", getAge: () => 27 };
console.log(touched.name, getters.getName(), getters.getAge());
true Hira 27

Conditional Types

Conditional types choose a type based on a condition: T extends U ? X : Y. The infer keyword captures part of a type.

type IsString<T> = T extends string ? "yes" : "no";
type ElementOf<T> = T extends (infer E)[] ? E : never;

const a: IsString<"hi"> = "yes";
const b: IsString<42> = "no";
const c: ElementOf<number[]> = 5;
console.log(a, b, c);
yes no 5

Frequently asked questions

What does keyof do in TypeScript?

It takes an object type and returns a union of its keys as string (or number/symbol) literal types.

What is the difference between typeof in JavaScript and TypeScript?

In expressions, typeof is the JavaScript operator that returns a string at runtime. In type positions, TypeScript's typeof gives you the static type of a variable.

What are mapped types used for?

To transform every property of a type in the same way, such as making them optional, readonly or changing their value types.