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 ofT's keys.K extends keyof T—Kis constrained to one of those keys.T[K]— indexed access: the type of the property at keyK.
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
- Write an interface and inspect
keyofit. Assign a valid and an invalid key to akeyof-typed variable. - Derive a type from a value with
typeof. Change the value and confirm the derived type follows. - Build the
as constarray +typeof arr[number]literal union. Confirm it matches the array. - Use indexed access
T["prop"]to get a property type, and chain it into a nested type. - Compute
T[keyof T]and confirm it is the union of all value types. - Get an array's element type with
SomeArray[number]. - Re-read
getPropertyand label each of the three tools it uses. Then write your own type-safepluckthat gets a key from every object in an array.
Official documentation
- TypeScript — Keyof Type Operator —
keyof. - TypeScript — Typeof Type Operator — Type-level
typeof. - TypeScript — Indexed Access Types —
T[K].
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