RizTech Academy logo
RizTech Academy
The Type System in DepthLesson 1 of 630 min

keyof, typeof and indexed access types

This module is where TypeScript stops being "JavaScript with type annotations" and becomes a genuine type-level programming language — one where you compute new types from existing ones. It is the feature people actually choose TypeScript for, and it powers every utility type and typed library. It can look intimidating; taken one operator at a time, it is not. We start with the three foundations: keyof, typeof, and indexed access — the tools for pulling types out of other types.

keyof — the keys of a type, as a union

keyof T produces a union of T's property names (as string literals):

interface User {
  id: string;
  name: string;
  age: number;
}

type UserKeys = keyof User;   // "id" | "name" | "age"

keyof User is the type "id" | "name" | "age" — a literal union of the property names. This is enormously useful, because it lets you write code that works with "any key of this type" safely:

function getKeys<T>(obj: T): (keyof T)[] {
  return Object.keys(obj) as (keyof T)[];
}

let key: keyof User;
key = "name";     // fine — a real key
key = "email";    // error: Type '"email"' is not assignable to type 'keyof User'

You met keyof in the constraints lesson (K extends keyof T). Here it stands on its own: whenever you need "the set of valid property names for this type", that is keyof T. It stays in sync with the type — add a property to User and keyof User automatically includes it.

typeof — a type from a value

You know typeof as a runtime JavaScript operator (typeof x === "string"). TypeScript adds a type-level typeof: typeof someValue gives you the type of that value, computed from the value itself:

const config = {
  apiUrl: "https://api.example.com",
  timeout: 30,
  retries: 3,
};

type Config = typeof config;
// { apiUrl: string; timeout: number; retries: number }

typeof config extracts the inferred type of the config value — you did not write the Config type, you derived it from the object. This is the bridge from the value world to the type world, and it is the key to "one source of truth": define a value, derive its type, and they cannot drift apart. It is especially powerful with as const (from the literal-types lesson):

const STATUSES = ["pending", "paid", "shipped"] as const;
type Status = typeof STATUSES[number];   // "pending" | "paid" | "shipped"

typeof STATUSES is the tuple type; the [number] (indexed access, below) extracts the element type. One array, and a matching literal union derived from it — the pattern that replaces an enum, promised back in the enums lesson and now fully explained.

Indexed access — the type at a key

Indexed access — T[K] — gives you the type of the property at key K:

interface User {
  id: string;
  name: string;
  age: number;
  address: { city: string; pincode: string };
}

type Name = User["name"];          // string
type Age = User["age"];            // number
type Address = User["address"];    // { city: string; pincode: string }
type City = User["address"]["city"];   // string — chained, going deeper

User["name"] is string — the type of the name property. You can chain it (User["address"]["city"]) to reach into nested types. This looks like value indexing (user["name"]), but it operates on types — User["name"] is a type, not a value. It keeps a derived type in sync: if User.name changes to number, User["name"] follows.

Indexed access takes a union of keys too, giving a union of the property types:

type StringOrNumber = User["id" | "age"];   // string | number
type AllValues = User[keyof User];           // string | number | { city; pincode } — every value type

User[keyof User] is a wonderfully compact way to say "the union of all of User's property value types". And array element types come out with [number]:

type Numbers = number[];
type Element = Numbers[number];   // number — the element type of the array

SomeArray[number] is "the type of an element" — which is exactly the typeof STATUSES[number] trick above.

Putting them together — the type-safe getter, explained

The getProperty from the constraints lesson used all three; now you can read it fully:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
  • keyof T — the union of T's keys.
  • K extends keyof T — K is constrained to one of those keys.
  • T[K] — indexed access: the type of the property at key K.

So getProperty(user, "age") returns exactly User["age"], which is number. Every piece is one of this lesson's tools, and together they make a fully type-safe, precise property accessor. This combination — keyof to get keys, a constraint to pick one, indexed access to get its type — is the foundation on which mapped and conditional types (the next lessons) are built.

Why these matter

keyof, typeof, and indexed access are the primitives of type-level programming — the way you pull information out of existing types instead of re-declaring it. They are why utility types like Pick and Record can exist, why libraries can offer perfectly-typed APIs derived from your data, and why you can have a single source of truth for both a value and its type. On their own they are useful; combined with the mapped and conditional types coming next, they let you compute almost any type from any other. This is the deep end of TypeScript, and these three operators are the floor you stand on.

Check your work

What keyof T produces. A union of T's property names as string literals — e.g. "id" | "name" | "age".

Why keyof is useful. It gives "the set of valid keys for this type", stays in sync with the type, and enables safe key-based code.

What type-level typeof does. Gives the type of a value — typeof config derives a type from the value, bridging value world to type world.

The as const + typeof [number] pattern. Derives a literal union from a const array — the enum replacement.

What indexed access T[K] gives. The type of the property at key K — chainable (T["a"]["b"]), and it takes a union of keys.

What T[keyof T] means. The union of all of T's property value types.

What SomeArray[number] gives. The element type of the array.

How getProperty uses all three. keyof T for the keys, K extends keyof T to pick one, T[K] for the property type — a fully type-safe getter.

Practice

  1. Write an interface and inspect keyof it. Assign a valid and an invalid key to a keyof-typed variable.
  2. Derive a type from a value with typeof. Change the value and confirm the derived type follows.
  3. Build the as const array + typeof arr[number] literal union. Confirm it matches the array.
  4. Use indexed access T["prop"] to get a property type, and chain it into a nested type.
  5. Compute T[keyof T] and confirm it is the union of all value types.
  6. Get an array's element type with SomeArray[number].
  7. Re-read getProperty and label each of the three tools it uses. Then write your own type-safe pluck that gets a key from every object in an array.

Official documentation

Next: mapped types — transforming every property of a type.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship