keyof & Indexed Types

The Course
•
5 min read

keyof T is the union of property keys known on T. For Book with title:string and pages:number, keyof Book is “title” | “pages”. T[K] is an indexed-access type: it describes the value at key K, not an array position.

A generic getter can preserve the relationship: get<T, K extends keyof T>(record: T, key: K): T[K]. Read this left to right: choose a record type, choose one of its keys, and return the type belonging to that key. With key “pages”, the result is number; with “title”, it is string.

This differs from accepting key:string and returning string|number for every call. A precise relationship gives the caller useful information without an assertion. It still cannot prove unknown external key text belongs to the allowed set; validate that separately.

TypeScript’s typeof type query can derive a type from an existing local value, as in type Book = typeof book. That is a compile-time use of typeof, different from the runtime string returned by typeof book. Use named aliases when they make the domain clearer.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.

Predict, then runTypeScript
function get<T, K extends keyof T>(record: T, key: K): T[K] {
  return record[key];
}

const book = { title: "Types", pages: 120 };

console.log(get(book, "title").toUpperCase());
console.log(get(book, "pages").toFixed(0));

Enable JavaScript to edit and type-check this example.

Check your prediction
TYPES
120

Read the compiler error

The generic constraint ties this key to real fields of this record.

Find and fix the type errorTypeScript
function get<T, K extends keyof T>(record: T, key: K): T[K] {
  return record[key];
}

console.log(get({ title: "Types", pages: 120 }, "duration"));

Enable JavaScript to edit and type-check this example.

Check your diagnosis

Use a supported key or deliberately transform the record. No assertion can create the missing property.

Make the selected key determine the result type

A dynamic string and a known property key carry different evidence. K extends keyof T restricts the input key to names present on T; returning T[K] preserves the value type associated with the chosen key. A broad string|number return would throw away that relationship.

This helper does not validate arbitrary key text from a URL or form. The caller must narrow that input before using it as a known key. Also keep type-level indexed access separate from ordinary array indexing, where a position may be absent.

Make the selected key determine the result type: traceTypeScript
function get<T, K extends keyof T>(value: T, key: K): T[K] {
  return value[key];
}

const record = { title: "Types", pages: 120 };
const title = get(record, "title");
const pages = get(record, "pages");

console.log(title.toUpperCase());
console.log(pages + 1);

Enable JavaScript to edit and type-check this example.

Check your prediction
TYPES
121

The two calls produce different static result types from the same helper. This lets each caller use type-appropriate operations without a cast.

Make the selected key determine the result type: contrastTypeScript
const record = { title: "Types", pages: 0 };

type Key = keyof typeof record;

function read(key: Key): string | number {
  return record[key];
}

console.log(read("pages"));
console.log(typeof read("title"));

Enable JavaScript to edit and type-check this example.

Check your prediction
0
string

The non-generic read intentionally returns the whole value union because its parameter is the whole key union. Compare that with the per-call relationship in get before choosing an API shape.

Try the rule on new inputs

Call get with every real key, then a misspelled key. Explain why a caller variable typed as string needs additional evidence. Do not assert unknown external text as keyof T without checking it.

Your turn

Exercise

keyof & Indexed Types: practise

Write the generic get helper and use it with a task having label:string and done:boolean. Print Review and false. Attempting key “missing” should be rejected.

Your workspaceTypeScript
// The key type must depend on T, not be an unrelated string.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

Return the indexed value type rather than any.

Hint 2: a first step

Introduce K extends keyof T and return T[K].

Show one solution
One possible solutionTypeScript
function get<T, K extends keyof T>(record: T, key: K): T[K] {
  return record[key];
}

const task = { label: "Review", done: false };

console.log(get(task, "label"));
console.log(get(task, "done"));

Enable JavaScript to edit and type-check this example.

Review
false

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Try calling a string method on get(task,“done”) and read the diagnostic. Pass a union key and explain why the result is a union of possible property types. Do not assert arbitrary input text to keyof merely to silence an error.

Was this article helpful?

Your response is saved on this device.