TypeScript / THE COURSESearch
STAGE 02 / THE TYPE SYSTEM

LESSON 12 OF 36 · 24 MIN + PRACTICE

keyof, typeof & indexed access

Keep property names and their values connected in reusable APIs.

EXAMPLES CHECKED WITH TYPESCRIPT 7.0.2 / NODE 24

Where you are

Generics can retain a whole input type. Sometimes the contract needs one of its keys or one of its property types. Type operators let you derive those relationships instead of duplicating definitions that drift.

Mental model

keyof T forms a union of keys visible in T. Type-position typeof value asks for the statically known type of an existing value. T[K] looks up a property type. These are type operations; runtime typeof is a JavaScript expression producing a category string.

JavaScript reality

Objects can have string and symbol keys; numeric property access is coerced for ordinary objects. Object.keys returns own enumerable string keys, excluding symbols and inherited properties. Its result is not safely every key of an arbitrary static object type, because the runtime object may have additional properties hidden by a narrower view.

TypeScript model

A generic key constrained by keyof T ensures a legal property lookup. Returning T[K] preserves the particular selected property's type. Returning a broad union of all property values would lose that relationship.

For arrays, indexed access with number retrieves the element type. For a readonly tuple of literals, it produces their literal union. String index signatures affect keyof because number keys can address the same runtime properties. Unions and intersections can produce less intuitive sets of usable keys; ask which operations are safe on every possible value.

Working example

Source: examples/valid/12.ts

TypeScript
function get<T, K extends keyof T>(value: T, key: K): T[K] {
  return value[key];
}
const job = { id: "j1", attempts: 2 };
export const attempts = get(job, "attempts");
export const phases = ["queued", "running", "done"] as const;
export type Phase = (typeof phases)[number];
console.log(attempts, get(job, "id"));
export {};

Verified runtime output:

text
2 j1

attempts is inferred as number, while id remains string. No overload is needed for each property. The phase union is derived from the maintained tuple so runtime configuration and static names share a source.

Type-checker drill

Intentionally invalid: examples/invalid/12.ts

TypeScript
function get<T, K extends keyof T>(value: T, key: K): T[K] {
  return value[key];
}
get({ id: "j1" }, "missing");
export {};

Actual TypeScript 7.0.2 diagnostic:

text
examples/invalid/12.ts(4,19): error TS2345: Argument of type '"missing"' is not assignable to parameter of type '"id"'.

The chosen key is outside the object's visible keys. Correct the spelling or extend the model deliberately. Accepting arbitrary strings would require a different contract, such as a dictionary whose lookups can be absent.

Runtime drill

Store a richer object behind a smaller type, call Object.keys on it, and inspect the extra runtime key. This explains why asserting every Object.keys result to Array<keyof T> is not a generally sound reusable helper. Keep a trusted key list or perform targeted property reads when the domain requires a closed set.

Professional pattern

Derive DTO field types from an owned schema only when that coupling is intended. Separate public DTOs when they have independent versioning or disclosure rules. A clever property accessor should improve the call site; normal dot access is clearer for a fixed property.

Exercise

Implement a property selector taking an object and one valid key. Add compile-time rejection for a misspelled key and verify the exact output type for two different properties. Derive a phase union from a readonly tuple and use it in a parser backed by the tuple's runtime values. Explain why deriving types does not itself validate a string.

Checkpoint

  • You distinguish runtime typeof from type-position typeof.
  • You preserve a selected key's exact value relationship.
  • You know Object.keys and keyof describe different things.
  • You can derive element unions without duplicating strings.

Sources

keyof, typeof, and indexed access.

Type explorer

These are the actual exported types emitted by TypeScript 7.0.2. Expand the declaration view to compare inferred and annotated types. This is a static compiler snapshot, not a live editor.

Inspect compiler-emitted types
TypeScript
export declare const attempts: number;
export declare const phases: readonly ["queued", "running", "done"];
export type Phase = (typeof phases)[number];
export {};

Check off this lesson when you can meet its checkpoint. Progress stays in this browser.