...

/

Type Relationships: keyof, Indexed Access, and typeof

Type Relationships: keyof, Indexed Access, and typeof

Master type relationships in TypeScript by leveraging keyof, indexed access, and typeof for dynamic, type-safe design.

We have now crossed the line between simply writing types and truly engineering them. In the real world, data structures evolve constantly. If we hard-code types manually, every small change means updating types everywhere—making the codebase more fragile and error-prone. Instead, we want types that derive automatically from real values, staying accurate as the code evolves.

In this lesson, we step into a new level of TypeScript fluency: we will learn to reference types dynamically using keyof, extract parts of types using indexed access, and capture types from values using typeof. These three tools are essential for creating resilient, DRY, and scalable codebases.

Let’s get moving.

Understanding keyof

When we write keyof T, we ask TypeScript: “What are all the keys available on the type T?” The result is a union of property names, typically string literals, but also sometimes numbers or symbols.

These keys represent the property names that TypeScript considers valid for indexing into a value of type T. If you imagine writing value[key] in JavaScript, keyof T is TypeScript’s way of asking: “Which keys would that be safe for?” These keys might come from fields on an object, methods on a class, array indices, built-in prototype members, or even symbol properties.

This works for more than just plain objects. keyof can be applied to almost any type including arrays, functions, primitives, and even any or never. TypeScript gives us back whatever keys are accessible on that type.

Let’s walk through a few examples to see how flexible and consistent this behavior is:

Press + to interact
TypeScript 5.8.3
Files
/* Objects */
type Obj = { id: number; name: string };
type ObjKeys = keyof Obj; // "id" | "name"
/* Arrays and tuples */
type Arr = string[];
type ArrKeys = keyof Arr; // number | "length" | "push" | "pop" | ...
type Tup = [number, string];
type TupKeys = keyof Tup; // 0 | 1 | "length" | ...
/* Functions (call-only signatures expose no keys) */
type Func = () => void;
type FuncKeys = keyof Func; // never
/* Primitive wrapper types */
type StrKeys = keyof string; // number | typeof Symbol.iterator | "toString" | ...
type NumKeys = keyof number; // "toString" | "valueOf" | "toFixed" | ...
type BoolKeys = keyof boolean; // "valueOf"
/* Primitive literals behave like their wrappers */
type LiteralStrKeys = keyof "hi"; // number | typeof Symbol.iterator | "toString" | …
type LiteralNumKeys = keyof 123; // "toString" | "toFixed" | ...
/* Special utility types */
type AnyKeys = keyof any; // string | number | symbol
type UnknownKeys = keyof unknown; // never

Explanation:

  • Line 1: Declare Obj, a plain object type with two properties: id and name.

    • Line 2: keyof Obj yields "id" | "name"—a union of the exact keys defined in the object.

  • Line 6: Define Arr as a string array type (string[]).

    • Line 7: keyof Arr yields number | "length" | "push" | "pop" | ...— numeric indices plus all array method and property keys.

  • Line 9: Define Tup, a tuple with a number and a string.

    • Line 10: keyof Tup yields 0 | 1 | "length" | ...—fixed index positions appear as numeric literals, followed by array keys. ...

Access this course and 1400+ top-rated courses and projects.