An indexed access type looks up the type of a property on another type, with the same bracket syntax JavaScript uses for property access. Person["age"] is number, and Person["address"]["city"] is string.
The benefit is a single source of truth. Age is not a second copy of number: if Person["age"] changes to string, every type written as Person["age"] changes with it. Indexed access types are compile-time only; they emit no JavaScript.
Union Keys and T[keyof T]
The index can be a union of keys. The result is the union of the property types:
Product[keyof Product] is the type-level way to say "any property value of Product". Asking for a key that does not exist is a compile error: Product["price"] gives error TS2339: Property 'price' does not exist on type 'Product'.
Optional Properties Include undefined
With strictNullChecks (on under strict), the type of an optional property includes undefined, and so does its indexed access type:
type Profile = { name: string; nickname?: string };
type Nick = Profile["nickname"]; // string | undefined
To drop the undefined, wrap it: NonNullable<Profile["nickname"]> is string.
Array Element Types with T[number]
Arrays are indexed by numbers, so indexing an array type with number gives the element type. It also works through several levels:
This is useful when a type comes from somewhere you do not control, such as a generated API client: ApiResponse["data"]["users"][number] names the user type without anyone having exported it.
Tuples: Index by Position
A tuple type can be indexed with a specific position, with number for the union of all element types, and with "length" for its length as a literal type:
(typeof arr)[number]: A Union from a const Array
The most common real-world use: keep a list of allowed values as an array (so you can loop over it at runtime) and derive the union type from it (so the compiler checks it).
Two parts make it work. as const makes the array a readonly tuple of literal types; without it, ROLES is string[] and (typeof ROLES)[number] is just string. Then typeof turns the value into a type, and [number] collects its elements into a union. The parentheses are optional (typeof ROLES[number] means the same), but they make the order of operations clear.
Checking a String Against the Array
That readonly tuple has one surprise. Its includes method only accepts the element type, so you cannot pass an arbitrary string to it:
index.ts(6,25): error TS2345: Argument of type 'string' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
Widen the array to readonly string[] for the check, and turn the function into a type guard so a successful check narrows the value:
This pattern gives you one list for both jobs: the runtime check uses the array, and the type is derived from it, so they cannot drift apart.
Indexed Access in Generics: T[K]
Inside a generic, T[K] with K extends keyof T is the type of whichever property the caller picked:
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 28 },
];
const names = pluck(users, "name"); // string[]
const ages = pluck(users, "age"); // number[]
The K extends keyof T constraint is what makes T[K] legal. Without it, TypeScript cannot know that K is a key of T and reports error TS2536: Type 'K' cannot be used to index type 'T'.
Syntax at a Glance
| Written | Means | Example result |
|---|---|---|
T["key"] | type of one property | Person["age"] is number |
T["a" | "b"] | union of those property types | string | number |
T[keyof T] | union of all property types | every value type |
T["a"]["b"] | nested property | Person["address"]["city"] |
Arr[number] | array element type | User[] gives User |
Tup[0] | tuple element at a position | first element type |
Tup["length"] | tuple length as a literal | 3 |
(typeof arr)[number] | union of a const array's values | "admin" | "editor" |
T[typeof key] | index with a constant's type | same as T["name"] |
The index is always a type. Person[key] where key is a const variable fails with two errors: error TS2538: Type 'key' cannot be used as an index type. and error TS2749: 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?.
Frequently Asked Questions
What is an indexed access type in TypeScript?
A type written T[K] that looks up the type of property K on type T. With type Person = { name: string; age: number }, Person["age"] is number. It uses the same bracket syntax as property access in JavaScript, but works on types at compile time.
How do I get the type of an array element in TypeScript?
Index the array type with number: for type Users = User[], Users[number] is User. For an array value, combine it with typeof: (typeof users)[number].
How do I turn an array of strings into a union type?
Declare the array with as const so TypeScript keeps the literal values, then index it with number: const roles = ["admin", "editor"] as const; type Role = (typeof roles)[number]; gives "admin" | "editor". Without as const the element type is just string.
Why can't I use a variable as the index in an indexed access type?
The index must be a type. Person[key] with const key = "name" fails (errors TS2538 and TS2749) because key is a value. Write Person[typeof key], or use the literal directly: Person["name"].
What does T[keyof T] mean?
It indexes T with the union of all its keys, so the result is the union of all its property types. For { a: string; b: number } it is string | number. It is the type-level way to say "any value of this object".