The TypeScript "extends" Keyword Doesn't Extend Types

I was looking through the built in TypeScript definition file "lib.es5.d.ts" and found the definition "Pick".

//From T pick a set of properties K
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];

In more words: Pick<T,K> is a new type of object whose keys are the set K, which has to be a subset of the keys in T.

But when I tried to understand what it was from the code itself, I had some trouble, and the source of that trouble is simply the extends keyword. We usually use 'A extends B' to mean A has all the keys in B and maybe some more, which the English also suggests. But in reality K must be a subset of T's keys.

What is going on?

What is a Type?

A type is a set of values based on some rule.
e.g. Colour = { red orange yellow ... }

If A is a subtype of B, values that match A are a subset of values that match B. The definition of the subtype therefore has to be a narrower version of the supertype.

So one subtype of Colour could be TrafficLightColours = { red amber green }.

How Do Object Definitions Apply to This?

Usually when programmers think about types, it's in terms of sets of keys/attributes/properties of an object.

Consider the following: Using this definition, Cat = { age: number, name: string }, if we were to create a new type the same as Cat but with one or more keys removed, we would actually be adding to the set of values that could fit this type. And therefore the set of values that match the new type would be a superset of the types that match Cat.

If more keys are added to the specification, it makes the definition narrower by reducing the number of values that it could now match.

A subset of the keys makes a supertype. A superset of the keys makes a subtype. A type's set of values mustn't be confused with the set of keys sometimes used to define its set of values.

What Makes TypeScript Different?

TypeScript is unusual among modern programming languages in that it allows types to be defined as a specific set of values and not just with newly created constants (which is what enums are).

In most modern object oriented languages the extends keyword makes a subtype by way of augmenting the set of keys required.

TypeScript uses this keyword in the same way but also uses it for subtypes in general, which can understandably cause confusion. 

In TypeScript the extends keyword always narrows in one sense and sometimes widens in another sense.

Another Example In This Vein

In TypeScript a type can be defined as an intersection of other types.

type A = { x: number }
type B = { y: number }
type C = A & B

Because C is the intersection of A and B it only matches values that match A and match B. However, such a value looks like { x: 1, y: 2, ... }. The keys of a value that matches C have to be at least the union of the keys in A and the keys in B.

When seeing this for the first time in production, I was thinking in terms of sets of keys and thought that only the empty object could match because the intersection of their keys is the empty set.


Popular posts from this blog

TypeScript: Turn an Array of Values into a Type Union