Indexed access types (T[K])

Looks up the type of one or more properties on another type using bracket notation.

Signature
Type[Key]
  • Added in TypeScript 2.1

Parameters

ParameterTypeDescription
$TypetypeThe object, array or tuple type being indexed.
$KeytypeA type - a string or numeric literal, a union of them, number, or keyof Type. It must be a type, never a runtime variable.

Return value

The type of the named property, or a union when Key is a unionThe type stored at Key. When Key is a union of keys the result is the union of the corresponding property types; Type[keyof Type] therefore yields the union of all value types.

Errors

Indexing with a key the type does not have is a compile-time error: Property 'alve' does not exist on type 'Person'.

Changelog

  • PHP 2.1Introduced as "lookup types" together with keyof.
  • PHP 4.0Variadic tuple types made T[number] on labelled and spread tuples far more useful for deriving parameter element types.

Indexed access reads a type the same way you read a property: Person["age"] is whatever type age holds. The index has to be a type, which is the rule that catches everybody — const key = "age"; type Age = Person[key] fails, because key is a value. Write type Key = "age" instead, or index with typeof key.

When to reach for it

To avoid exporting a type for every nested shape. If you already export ApiResponse, you do not need a separate ApiUser — ApiResponse["user"] is that type, and it stays correct when the response changes.

The high-value form is T[number] on arrays and tuples, which pulls out the element type. Combined with a type query and as const it turns a literal array into a union: (typeof ROUTES)[number].

What trips people up

A union index gives a union result, so Person[keyof Person] is every value type at once — useful, but not a way to narrow. And on a tuple, T[0] is the first element's type while T[number] is the union of all of them; reaching for number when you wanted a specific slot silently widens what you get.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Derive a prop type from a component's props interface instead of exporting it separately
  • Turn a const array of route names into a union with (typeof ROUTES)[number]
  • Extract a nested payload type out of a large API response type

Frequently asked questions

How do I get the element type of an array type?

Index it with number: MyArray[number]. For an array value rather than a type, use (typeof myArray)[number], and add as const to the array if you want the elements as literal types instead of widened string or number.

Why does Person[key] fail when key is a const string?

Because the index position expects a type and a const is a value. TypeScript reports "'key' refers to a value, but is being used as a type". Write Person[typeof key], or declare type key = "age" and index with that.

How do I get a union of all the values in an object type?

Index by all of its keys: T[keyof T]. For a real object value the full form is (typeof obj)[keyof typeof obj], usually with as const on the object so the values stay literal.

More examples

Unions as the index

Each index distributes over the union it is given, so the result is the union of the matching property types. Person[keyof Person] is the idiom for "every value type in this object". The commented line shows that a typo is caught immediately rather than resolving to any.

Example · typescript
type Person = { age: number; name: string; alive: boolean };

type Age = Person["age"];
// type Age = number

type I1 = Person["age" | "name"];
// type I1 = string | number

type I2 = Person[keyof Person];
// type I2 = string | number | boolean

type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];
// type I3 = string | boolean

// type Oops = Person["alve"];
// error: Property 'alve' does not exist on type 'Person'

Arrays, tuples and the value-vs-type rule

On a tuple a numeric literal index picks one slot while number collapses to the union of every slot, which is the difference between First and Either. Chained accesses work exactly like chained property reads, so User["tags"][number] walks into the array and out again as string. The final block is the value-versus-type rule: a const is a value, and typeof is what lifts it into a position where indexing accepts it.

Example · typescript
type Pair = [id: number, label: string];
type First = Pair[0];       // number
type Either = Pair[number]; // number | string

type Users = { id: number; tags: string[] }[];
type User = Users[number];      // { id: number; tags: string[] }
type Tag = User["tags"][number]; // string

const key = "id";
// type Bad = User[key];
// error: 'key' refers to a value, but is being used as a type

type Good = User[typeof key]; // number
type AlsoGood = User["id"];   // number

Discussion

  • Be the first to comment on this lesson.