What does [keyof T] do at the end of a type = {} declaration?

type Foo<T> = {  
  // pretend we have a type definition for Q here
}[keyof T];

What does the [keyof T] do at the end there?

Short Answer

When placed at the end of a type declaration, [keyof T] creates an indexed access type in which the index to lookup is an indexed type query on T.

That is a mouthful. Here it is in two steps:

  • First create a union type of the property names of T.
  • e.g. "one" | "two" | "three"
  • then get the types of those properties from type Q.
  • e.g. string | number | boolean

keyof T does the first step and [] does the second step.

Explanation

The ~ TypeScript 2.1 release notes say that an "indexed type query keyof T yields the type of permitted property names for T." The use of the word "yield" makes an indexed type query sound like a generator of the properties names of T.

// given a type like this
interface Model {  
  one: string,
  two: number,
  three: boolean,
}

// the generator produces a union type of property names
type Props = keyof Model; // "one" | "two" | "three"  

When keyof T is in brackets [] at the end of a type, it is acting as an index access type. An index access type gets the type of a property by name.

type TypeOfOne = Model["one"]; // string  
type TypeOfTwo = Model["two"]; // number  
type TypeOfThree = Model["three"]; // boolean  

An index access type can also lookup the type of a union of property names:

// string | number | boolean
type TypeOfOneTwoOrThree = Model["one" | "two" | "three"];  

That is what { /* some type Q */ }[keyof T] is doing.

  1. generate a union type of property names of T,
  2. turn that into a union type of those properties' types in Q.

Manual Type Expansion

// initial example
type Foo<T> = {  
  [K in keyof T]: T[K]
}[keyof T];

// usage
interface Model {  
  one: string,
  two: number,
  three: boolean,
}

// result
type FooModel = Foo<Model>; // string | number | boolean  

Expanding the initial example manually looks like this:

// replace T with Model
type FooModel1 = {  
  [K in keyof Model]: Model[K]
}[keyof Model];

// replace keyof Model with the keys
type FooModel2 = {  
  [K in "one" | "two" | "three"]: Model[K]
}["one" | "two" | "three"];

// replace K with each key instance
type FooModel3 = {  
  one: Model["one"];
  two: Model["two"];
  three: Model["three"];
}["one" | "two" | "three"];

// replace Model["key"] with its type
type FooModel4 = {  
  one: string;
  two: number;
  three: boolean;
}["one" | "two" | "three"];

// expand the lookup type union
type FooModel6 = string | number | boolean;