keyof, typeof & Indexed Access

TypeScript · Lesson 16 of 28 6 min read Updated

So far you have written most types by hand. Large programs work better when types are derived: from a value that already exists, or from another type. Then a change in one place updates every type that depends on it.

This lesson covers three small operators that make this possible. typeof gets the type of a value, keyof gets the property names of a type, and indexed access types such as Order["items"] get the type of a property. Together they power most of the advanced types you will meet later.

typeof: the type of a value

In JavaScript, typeof x is a run-time operator that returns a string such as "number". TypeScript adds a second meaning: in a type position (after a colon, in a type alias), typeof x means "the type of the variable x".

TypeScriptRun it yourself
const defaultSettings = {
  language: "en",
  fontSize: 16,
  notifications: { sms: true, email: false },
};

type Settings = typeof defaultSettings;
// { language: string; fontSize: number; notifications: { sms: boolean; email: boolean } }

function describe(s: Settings): string {
  const channels = Object.entries(s.notifications)
    .filter(([, on]) => on)
    .map(([name]) => name);
  return `${s.language}, ${s.fontSize}px, alerts by ${channels.join(" and ")}`;
}

const priyaSettings: Settings = {
  language: "hi",
  fontSize: 18,
  notifications: { sms: true, email: true },
};

console.log(describe(defaultSettings));
console.log(describe(priyaSettings));
console.log(typeof defaultSettings.fontSize); // the JavaScript typeof
Output
en, 16px, alerts by sms
hi, 18px, alerts by sms and email
number

The last line uses the ordinary JavaScript typeof, which runs and prints a string. The type Settings = typeof defaultSettings line is the TypeScript version, which exists only while compiling. TypeScript tells them apart by where they appear.

Mixing up values and types is a common error:

TypeScript
const config = { retries: 3, timeoutMs: 5000 };

function connect(options: config) {}
Output
app.ts(3,27): error TS2749: 'config' refers to a value, but is being used as a type here. Did you mean 'typeof config'?

The message even suggests the fix. You also need typeof for functions, as in ReturnType<typeof fetchSeatMap> from Utility Types.

keyof: the property names of a type

keyof T produces a union of the property names of T, as string literal types.

TypeScriptRun it yourself
type Student = { name: string; marks: number; city: string };

type StudentKey = keyof Student; // "name" | "marks" | "city"

function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const meera: Student = { name: "Meera", marks: 93, city: "Kochi" };

const name = getField(meera, "name");   // type: string
const marks = getField(meera, "marks"); // type: number

console.log(name.toUpperCase(), marks + 5);

const columns: StudentKey[] = ["name", "city"];
for (const col of columns) {
  console.log(`${col}: ${meera[col]}`);
}
Output
MEERA 98
name: Meera
city: Kochi

getField is a fully typed version of obj[key]: the key must be a real property name, and the result has the type of that property. That return type, T[K], is an indexed access type, explained below.

A very common combination is keyof typeof, which gets the keys of an object value:

TypeScript
const fares = { SL: 450, "3A": 1200, "2A": 1750 };
type Coach = keyof typeof fares; // "SL" | "3A" | "2A"

Indexed access types

An indexed access type looks up the type of a property, using the same square brackets you use to read a property at run time: Order["customer"] is the type of the customer property of Order.

TypeScriptRun it yourself
type Order = {
  id: number;
  customer: { name: string; phone: string };
  items: { name: string; price: number; qty: number }[];
  status: "placed" | "packed" | "delivered";
};

type Customer = Order["customer"];   // { name: string; phone: string }
type Item = Order["items"][number];  // one element of the items array
type Status = Order["status"];       // "placed" | "packed" | "delivered"
type IdOrStatus = Order["id" | "status"]; // number | "placed" | "packed" | "delivered"

function lineTotal(item: Item): number {
  return item.price * item.qty;
}

function label(c: Customer, s: Status): string {
  return `${c.name} (${c.phone}) - ${s}`;
}

const order: Order = {
  id: 9001,
  customer: { name: "Farhan", phone: "99887 76655" },
  items: [{ name: "Kurta", price: 899, qty: 2 }, { name: "Dupatta", price: 349, qty: 1 }],
  status: "packed",
};

console.log(order.items.map(lineTotal));
console.log(label(order.customer, order.status));
Output
[ 1798, 349 ]
Farhan (99887 76655) - packed

Two details are worth remembering:

  • Order["items"][number] means "the type you get when you index the array with any number", which is the element type. This is how you get the item type of an array type.
  • Indexing with a union, as in Order["id" | "status"], gives a union of the property types.

You cannot use a variable inside the brackets: Order[key] where key is a const value is an error. Use a type, such as Order[typeof key] or a literal like Order["status"].

Putting them together

Here is the pattern from Literal Types, as const & satisfies, now fully explained. One as const array is the single source of truth, and every type is derived from it:

TypeScriptRun it yourself
const MENU = [
  { code: "MD", name: "Masala dosa", price: 90 },
  { code: "ID", name: "Idli vada", price: 70 },
  { code: "FC", name: "Filter coffee", price: 30 },
] as const;

type MenuItem = (typeof MENU)[number];
type MenuCode = MenuItem["code"]; // "MD" | "ID" | "FC"

function priceOf(code: MenuCode): number {
  const item = MENU.find((m) => m.code === code);
  return item ? item.price : 0;
}

const order: Record<MenuCode, number> = { MD: 2, ID: 1, FC: 3 };

let total = 0;
for (const code of Object.keys(order) as MenuCode[]) {
  total += priceOf(code) * order[code];
}
console.log(`Bill: ₹${total}`);
Output
Bill: ₹340

Add a fourth item to MENU and MenuCode gains a new member automatically; order then reports a missing key until you add it.

Why Object.keys(order) as MenuCode[]? Because Object.keys() always returns string[]. TypeScript's structural typing means an object may have more properties than its type shows, so the keys could be any strings. When you are sure the object has exactly the declared keys, as here, the assertion is reasonable.

Common mistakes

  • Using a value as a type. Write typeof config, not config, in type positions.
  • Confusing keyof typeof obj and typeof obj. The first gives the key names; the second gives the whole object type.
  • Forgetting [number] for arrays. Order["items"] is the array type; Order["items"][number] is one item.
  • Expecting Object.keys to return keyof T. It returns string[]; assert only when you know there are no extra keys.

Key points

  • In a type position, typeof x gives the type of the value x; in normal code it is still the JavaScript operator.
  • keyof T is a union of the property names of T; keyof typeof obj gets the keys of an object value.
  • T["prop"] gets a property's type, T[number] gets an array's element type, and a union index gives a union of types.
  • <T, K extends keyof T>(obj: T, key: K): T[K] is the standard pattern for typed property access.
  • Deriving types from one as const source keeps data and types in sync.

Exercise

A railway app in Jaipur stores fares as const FARES = { SL: 450, "3A": 1200, "2A": 1750, "1A": 2900 } as const;. Derive a type Coach for the keys and a type Fare for the values. Write cheapestAbove(minFare: number): Coach | undefined that returns the cheapest coach whose fare is at least minFare. Log the result for 1000 and for 5000.

Show solution
TypeScript
const FARES = { SL: 450, "3A": 1200, "2A": 1750, "1A": 2900 } as const;

type Coach = keyof typeof FARES;          // "SL" | "3A" | "2A" | "1A"
type Fare = (typeof FARES)[Coach];        // 450 | 1200 | 1750 | 2900

function cheapestAbove(minFare: number): Coach | undefined {
  const coaches = Object.keys(FARES) as Coach[];
  const eligible = coaches.filter((c) => FARES[c] >= minFare);
  return eligible.toSorted((a, b) => FARES[a] - FARES[b])[0];
}

const sleeper: Fare = FARES.SL;
console.log(sleeper);              // 450
console.log(cheapestAbove(1000));  // 3A
console.log(cheapestAbove(5000));  // undefined

keyof typeof FARES gives the coach names and indexing the object type with them gives the fare values, so both types come straight from the data.

Quick Quiz

1. What does type T = typeof settings do when written in a type alias?

2. For type P = { id: number; name: string }, what is keyof P?

3. How do you get the element type of type Cart = { items: Product[] }?

4. What does Object.keys(obj) return in TypeScript?

Learn TypeScript with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call